├── .clang-format
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .travis.yml
├── ALGORITHM.md
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── contrib
├── fzy-dvtm
└── fzy-tmux
├── deps
├── greatest
│ ├── greatest.h
│ └── package.json
└── theft
│ ├── .gitignore
│ ├── LICENSE
│ ├── theft.c
│ ├── theft.h
│ ├── theft_bloom.c
│ ├── theft_bloom.h
│ ├── theft_hash.c
│ ├── theft_mt.c
│ ├── theft_mt.h
│ ├── theft_types.h
│ └── theft_types_internal.h
├── fzy.1
├── src
├── bonus.h
├── choices.c
├── choices.h
├── config.def.h
├── fzy.c
├── match.c
├── match.h
├── options.c
├── options.h
├── tty.c
├── tty.h
├── tty_interface.c
└── tty_interface.h
└── test
├── acceptance
├── Gemfile
├── Gemfile.lock
└── acceptance_test.rb
├── fzytest.c
├── test_choices.c
├── test_match.c
└── test_properties.c
/.clang-format:
--------------------------------------------------------------------------------
1 | IndentWidth: 8
2 | UseTab: Always
3 | BreakBeforeBraces: Attach
4 | IndentCaseLabels: true
5 | AllowShortFunctionsOnASingleLine: false
6 | ColumnLimit: 100
7 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | name: Test ${{ matrix.compiler }} on ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | compiler: [gcc, clang]
11 | os: [ubuntu-latest, macOS-latest]
12 | runs-on: ${{ matrix.os }}
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Compile and run tests
16 | run: make && make test
17 | env:
18 | CC: ${{ matrix.compiler }}
19 | test_alpine:
20 | name: Test on Alpine Linux
21 | runs-on: ubuntu-latest
22 | container: docker://alpine
23 | steps:
24 | - uses: actions/checkout@v2
25 | - name: Install build dependencies
26 | run: apk add build-base
27 | - name: Compile and run tests
28 | run: make && make test
29 | multiarch_test:
30 | name: Test on ${{ matrix.arch }}
31 | strategy:
32 | matrix:
33 | arch: [armv7, aarch64, s390x, ppc64le]
34 | runs-on: ubuntu-20.04
35 | steps:
36 | - uses: actions/checkout@v2
37 | - uses: uraimo/run-on-arch-action@v2.0.5
38 | name: Compile and run tests
39 | id: test
40 | with:
41 | arch: ${{ matrix.arch }}
42 | distro: ubuntu20.04
43 | githubToken: ${{ github.token }}
44 | install: |
45 | apt-get update -q -y
46 | apt-get install -y gcc make
47 | rm -rf /var/lib/apt/lists/*
48 | run: make && make test
49 | acceptance_test:
50 | name: Acceptance Tests
51 | runs-on: ubuntu-latest
52 | steps:
53 | - uses: actions/checkout@v2
54 | - name: Set up Ruby 2.6
55 | uses: actions/setup-ruby@v1
56 | with:
57 | ruby-version: 2.6.x
58 | - name: Install dependencies and compile
59 | run: |
60 | gem install bundler
61 | make
62 | cd test/acceptance && bundle install --jobs 4 --retry 3
63 | - name: Run acceptance tests
64 | run: make acceptance
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | fzy
2 | fzytest
3 | *.o
4 | *.d
5 | config.h
6 | test/acceptance/vendor/bundle
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: false
3 | language: c
4 |
5 | matrix:
6 | include:
7 | - os: linux
8 | arch: amd64
9 | compiler: clang
10 | - os: linux
11 | arch: ppc64le
12 | compiler: clang
13 | - os: osx
14 | arch: amd64
15 | compiler: clang
16 | - os: linux
17 | arch: amd64
18 | compiler: gcc
19 | - os: linux
20 | arch: ppc64le
21 | compiler: gcc
22 | - os: osx
23 | arch: amd64
24 | compiler: gcc
25 | script: make && make check
26 | jobs:
27 | include:
28 | - stage: Acceptance Test
29 | language: ruby
30 | rvm: 2.5.1
31 | script: make acceptance
32 | addons:
33 | apt:
34 | packages:
35 | - tmux
36 |
--------------------------------------------------------------------------------
/ALGORITHM.md:
--------------------------------------------------------------------------------
1 |
2 | This document describes the scoring algorithm of fzy as well as the algorithm
3 | of other similar projects.
4 |
5 | # Matching vs Scoring
6 |
7 | I like to split the problem a fuzzy matchers into two subproblems: matching and scoring.
8 |
9 | Matching determines which results are eligible for the list.
10 | All the projects here consider this to be the same problem, matching the
11 | candidate strings against the search string with any number of gaps.
12 |
13 | Scoring determines the order in which the results are sorted.
14 | Since scoring is tasked with finding what the human user intended, there is no
15 | correct solution. As a result there are large variety in scoring strategies.
16 |
17 | # fzy's matching
18 |
19 | Generally, more time is taken in matching rather than scoring, so it is
20 | important that matching be as fast as possible. If this were case sensitive it
21 | would be a simple loop calling strchr, but since it needs to be case
22 | insensitive.
23 |
24 | # fzy's scoring
25 |
26 | fzy treats scoring as a modified [edit
27 | distance](https://en.wikipedia.org/wiki/Edit_distance) problem of calculating
28 | the
29 | [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance).
30 | Edit distance is the measure of how different two strings are in terms of
31 | insertions, deletions, and substitutions. This is the same problems as [DNA
32 | sequence alignment](https://en.wikipedia.org/wiki/Sequence_alignment). Fuzzy
33 | matching is a simpler problem which only accepts insertions, not deletions or
34 | substitutions.
35 |
36 | fzy's scoring is a dynamic programming algorithm similar to
37 | [Wagner–Fischer](https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm)
38 | and
39 | [Needleman–Wunsch](https://en.wikipedia.org/wiki/Needleman%E2%80%93Wunsch_algorithm).
40 |
41 | Dynamic programming requires the observation that the result is based on the
42 | result of subproblems.
43 |
44 | Fzy borrows heavily from concepts in bioinformatics to performs scoring.
45 |
46 | Fzy builds a `n`-by-`m` matrix, where `n` is the length of the search string
47 | and `m` the length of the candidate string. Each position `(i,j)` in the matrix
48 | stores the score for matching the first `i` characters of the search with the
49 | first `j` characters of the candidate.
50 |
51 | Fzy calculates an affine gap penalty, this means simply that we assign a
52 | constant penalty for having a gap and a linear penalty for the length of the
53 | gap.
54 | Inspired by the [Gotoh algorithm
55 | (pdf)](http://www.cs.unibo.it/~dilena/LabBII/Papers/AffineGaps.pdf), fzy
56 | computes a second `D` (for diagonal) matrix in parallel with the score matrix.
57 | The `D` matrix computes the best score which *ends* in a match. This allows
58 | both computation of the penalty for starting a gap and the score for a
59 | consecutive match.
60 |
61 | Using [this
62 | algorithm](https://github.com/jhawthorn/fzy/blob/master/src/match.c#L105) fzy
63 | is able to score based on the optimal match.
64 |
65 | * Gaps (negative score)
66 | * at the start of the match
67 | * at the end of the match
68 | * within the match
69 | * Matches (positive score)
70 | * consecutive
71 | * following a slash
72 | * following a space, underscore, or dash (the start of a word)
73 | * capital letter (the start of a CamelCase word)
74 | * following a dot (often a file extension)
75 |
76 |
77 |
78 | # Other fuzzy finders
79 |
80 | ## TextMate
81 |
82 | TextMate deserves immense credit for popularizing fuzzy finding from inside
83 | text editors. It's influence can be found in the command-t project, various
84 | other editors use command-t for file finding, and the 't' command in the github
85 | web interface.
86 |
87 | * https://github.com/textmate/textmate/blob/master/Frameworks/text/src/ranker.cc
88 |
89 | ## command-t, ctrlp-cmatcher
90 |
91 | Command-t is a plugin first released in 2010 intending to bring TextMate's
92 | "Go to File" feature to vim.
93 |
94 | Anecdotally, this algorithm works very well. The recursive nature makes it a little hard to
95 |
96 | The wy `last_idx` is suspicious.
97 |
98 | * https://github.com/wincent/command-t/blob/master/ruby/command-t/match.c
99 | * https://github.com/JazzCore/ctrlp-cmatcher/blob/master/autoload/fuzzycomt.c
100 |
101 | ## Length of shortest first match: fzf
102 | https://github.com/junegunn/fzf/blob/master/src/algo/algo.go
103 |
104 | Fzy scores based on the size of the greedy shortest match. fzf finds its match
105 | by the first match appearing in the candidate string. It has some cleverness to
106 | find if there is a shorter match contained in that search, but it isn't
107 | guaranteed to find the shortest match in the string.
108 |
109 | Example results for the search "abc"
110 |
111 | * **AXXBXXC**xxabc
112 | * xxxxxxx**AXBXC**
113 | * xxxxxxxxx**ABC**
114 |
115 | ## Length of first match: ctrlp, pick, selecta (`<= 0.0.6`)
116 |
117 | These score based on the length of the first match in the candidate. This is
118 | probably the simplest useful algorithm. This has the advantage that the heavy
119 | lifting can be performed by the regex engine, which is faster than implementing
120 | anything natively in ruby or Vim script.
121 |
122 | ## Length of shortest match: pick
123 |
124 | Pick has a method, `min_match`, to find the absolute shortest match in a string.
125 | This will find better results than the finders, at the expense of speed, as backtracking is required.
126 |
127 | ## selecta (latest master)
128 | https://github.com/garybernhardt/selecta/commit/d874c99dd7f0f94225a95da06fc487b0fa5b9edc
129 | https://github.com/garybernhardt/selecta/issues/80
130 |
131 | Selecta doesn't compare all possible matches, but only the shortest match from the same start location.
132 | This can lead to inconsistent results.
133 |
134 | Example results for the search "abc"
135 |
136 | * x**AXXXXBC**
137 | * x**ABXC**x
138 | * x**ABXC**xbc
139 |
140 | The third result here should have been scored the same as the first, but the
141 | lower scoring but shorter match is what is measured.
142 |
143 |
144 | ## others
145 |
146 | * https://github.com/joshaven/string_score/blob/master/coffee/string_score.coffee (first match + heuristics)
147 | * https://github.com/atom/fuzzaldrin/blob/master/src/scorer.coffee (modified version of string_score)
148 | * https://github.com/jeancroy/fuzzaldrin-plus/blob/master/src/scorer.coffee (Smith Waterman)
149 |
150 |
151 | # Possible fzy Algorithm Improvements
152 |
153 | ## Case sensitivity
154 |
155 | fzy currently treats all searches as case-insensitive. However, scoring prefers
156 | matches on uppercase letters to help find CamelCase candidates. It might be
157 | desirable to support a case sensitive flag or "smart case" searching.
158 |
159 | ## Faster matching
160 |
161 | Matching is currently performed using the standard lib's `strpbrk`, which has a
162 | very simple implementation (at least in glibc).
163 |
164 | Glibc has an extremely clever `strchr` implementation which searches the haystack
165 | string by [word](https://en.wikipedia.org/wiki/Word_(computer_architecture)), a
166 | 4 or 8 byte `long int`, instead of by byte. It tests if a word is likely to
167 | contain either the search char or the null terminator using bit twiddling.
168 |
169 | A similar method could probably be written to perform to find a character in a
170 | string case-insensitively.
171 |
172 | * https://sourceware.org/git/?p=glibc.git;a=blob;f=string/strchr.c;h=f73891d439dcd8a08954fad4d4615acac4e0eb85;hb=HEAD
173 |
174 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.0 (2018-09-23)
2 |
3 | Features:
4 |
5 | - Support UTF-8
6 | - Support readline-like editing
7 | - Quit on Esc
8 | - Redraw on terminal resize
9 | - Bracketed paste escapes are ignored
10 |
11 | Performance:
12 |
13 | - Initialize tty interface before reading stdin
14 |
15 | ## 0.9 (2017-04-17)
16 |
17 | Features:
18 |
19 | - Support Ctrl-k and Ctrl-j for movement
20 |
21 | Performance:
22 |
23 | - Use threads to parallelize sorting
24 | - Improve parallelism of searching and scoring
25 |
26 | Internal:
27 |
28 | - Fix test suite on i386
29 | - Replace test suite with greatest
30 | - Add property tests
31 | - Add acceptance tests
32 |
33 | ## 0.8 (2017-01-01)
34 |
35 | Bugfixes:
36 |
37 | - Fix cursor position shifing upwards when input has less than 2 items.
38 |
39 | ## 0.7 (2016-08-03)
40 |
41 | Bugfixes:
42 |
43 | - Fixed a segfault when encountering non-ascii characters
44 | - Fixed building against musl libc
45 |
46 | ## 0.6 (2016-07-26)
47 |
48 | Performance:
49 |
50 | - Use threads to parallelize searching and scoring
51 | - Read all pending input from tty before searching
52 | - Use a lookup table for computing bonuses
53 |
54 | Bugfixes:
55 |
56 | - Fixed command line parsing on ARM
57 | - Fix error when autocompleting and there are no matches
58 |
59 | ## 0.5 (2016-06-11)
60 |
61 | Bugfixes:
62 |
63 | - Made sorting stable on all platforms
64 |
65 | ## 0.4 (May 19, 2016)
66 |
67 | Features:
68 |
69 | - Add `-q`/`--query` for specifying initial query
70 |
71 | Bugfixes:
72 |
73 | - Fixed last line of results not being cleared on exit
74 | - Check errors when opening the TTY device
75 |
76 | ## 0.3 (April 25, 2016)
77 |
78 | Bugfixes:
79 |
80 | - Runs properly in a terminal with -icrnl
81 |
82 | ## 0.2 (October 19, 2014)
83 |
84 | Features:
85 |
86 | - Allow specifying custom prompt
87 |
88 | Performance:
89 |
90 | - Reduce memory usage on large sets
91 |
92 | Bugfixes:
93 |
94 | - Terminal is properly reset on exit
95 | - Fixed make install on OS X
96 |
97 | ## 0.1 (September 20, 2014)
98 |
99 | Initial release
100 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 John Hawthorn
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | VERSION=1.0
2 |
3 | CPPFLAGS=-DVERSION=\"${VERSION}\" -D_GNU_SOURCE
4 | CFLAGS+=-MD -Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps -Werror=vla
5 | PREFIX?=/usr/local
6 | MANDIR?=$(PREFIX)/share/man
7 | BINDIR?=$(PREFIX)/bin
8 | DEBUGGER?=
9 |
10 | INSTALL=install
11 | INSTALL_PROGRAM=$(INSTALL)
12 | INSTALL_DATA=${INSTALL} -m 644
13 |
14 | LIBS=-lpthread
15 | OBJECTS=src/fzy.o src/match.o src/tty.o src/choices.o src/options.o src/tty_interface.o
16 | THEFTDEPS = deps/theft/theft.o deps/theft/theft_bloom.o deps/theft/theft_mt.o deps/theft/theft_hash.o
17 | TESTOBJECTS=test/fzytest.c test/test_properties.c test/test_choices.c test/test_match.c src/match.o src/choices.o src/options.o $(THEFTDEPS)
18 |
19 | all: fzy
20 |
21 | test/fzytest: $(TESTOBJECTS)
22 | $(CC) $(CFLAGS) $(CCFLAGS) -Isrc -o $@ $(TESTOBJECTS) $(LIBS)
23 |
24 | acceptance: fzy
25 | cd test/acceptance && bundle --quiet && bundle exec ruby acceptance_test.rb
26 |
27 | test: check
28 | check: test/fzytest
29 | $(DEBUGGER) ./test/fzytest
30 |
31 | fzy: $(OBJECTS)
32 | $(CC) $(CFLAGS) $(CCFLAGS) -o $@ $(OBJECTS) $(LIBS)
33 |
34 | %.o: %.c config.h
35 | $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
36 |
37 | config.h: src/config.def.h
38 | cp src/config.def.h config.h
39 |
40 | install: fzy
41 | mkdir -p $(DESTDIR)$(BINDIR)
42 | cp fzy $(DESTDIR)$(BINDIR)/
43 | chmod 755 ${DESTDIR}${BINDIR}/fzy
44 | mkdir -p $(DESTDIR)$(MANDIR)/man1
45 | cp fzy.1 $(DESTDIR)$(MANDIR)/man1/
46 | chmod 644 ${DESTDIR}${MANDIR}/man1/fzy.1
47 |
48 | fmt:
49 | clang-format -i src/*.c src/*.h
50 |
51 | clean:
52 | rm -f fzy test/fzytest src/*.o src/*.d deps/*/*.o
53 |
54 | veryclean: clean
55 | rm -f config.h
56 |
57 | .PHONY: test check all clean veryclean install fmt acceptance
58 |
59 | -include $(OBJECTS:.o=.d)
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | **fzy** is a fast, simple fuzzy text selector for the terminal with an advanced scoring algorithm.
4 |
5 | [Try it out online!](http://jhawthorn.github.io/fzy-demo)
6 |
7 | 
8 |
9 |
10 | It's been kind of life-changing.
11 | -@graygilmore
12 |
13 |
14 |
15 | fzy works great btw
16 | -@alexblackie
17 |
18 |
19 | [](https://github.com/jhawthorn/fzy/actions)
20 |
21 | ## Why use this over fzf, pick, selecta, ctrlp, ...?
22 |
23 | fzy is faster and shows better results than other fuzzy finders.
24 |
25 | Most other fuzzy matchers sort based on the length of a match. fzy tries to
26 | find the result the user intended. It does this by favouring matches on
27 | consecutive letters and starts of words. This allows matching using acronyms or
28 | different parts of the path.
29 |
30 | A gory comparison of the sorting used by fuzzy finders can be found in [ALGORITHM.md](ALGORITHM.md)
31 |
32 | fzy is designed to be used both as an editor plugin and on the command line.
33 | Rather than clearing the screen, fzy displays its interface directly below the current cursor position, scrolling the screen if necessary.
34 |
35 | ## Installation
36 |
37 | **macOS**
38 |
39 | Using Homebrew
40 |
41 | brew install fzy
42 |
43 | Using MacPorts
44 |
45 | sudo port install fzy
46 |
47 | **[Arch Linux](https://www.archlinux.org/packages/?sort=&q=fzy&maintainer=&flagged=)/MSYS2**: `pacman -S fzy`
48 |
49 | **[FreeBSD](https://www.freebsd.org/cgi/ports.cgi?query=fzy&stype=all)**: `pkg install fzy`
50 |
51 | **[Gentoo Linux](https://packages.gentoo.org/packages/app-shells/fzy)**: `emerge -av app-shells/fzy`
52 |
53 | **[Ubuntu](https://packages.ubuntu.com/search?keywords=fzy&searchon=names&suite=bionic§ion=all)/[Debian](https://packages.debian.org/search?keywords=fzy&searchon=names&suite=all§ion=all)**: `apt-get install fzy`
54 |
55 | **[pkgsrc](http://pkgsrc.se/misc/fzy) (NetBSD and others)**: `pkgin install fzy`
56 |
57 | **[openSUSE](https://software.opensuse.org/package/fzy)**: `zypper in fzy`
58 |
59 | ### From source
60 |
61 | make
62 | sudo make install
63 |
64 | The `PREFIX` environment variable can be used to specify the install location,
65 | the default is `/usr/local`.
66 |
67 | ## Usage
68 |
69 | fzy is a drop in replacement for [selecta](https://github.com/garybernhardt/selecta), and can be used with its [usage examples](https://github.com/garybernhardt/selecta#usage-examples).
70 |
71 | ### Use with Vim
72 |
73 | fzy can be easily integrated with vim.
74 |
75 | ``` vim
76 | function! FzyCommand(choice_command, vim_command)
77 | try
78 | let output = system(a:choice_command . " | fzy ")
79 | catch /Vim:Interrupt/
80 | " Swallow errors from ^C, allow redraw! below
81 | endtry
82 | redraw!
83 | if v:shell_error == 0 && !empty(output)
84 | exec a:vim_command . ' ' . output
85 | endif
86 | endfunction
87 |
88 | nnoremap e :call FzyCommand("find . -type f", ":e")
89 | nnoremap v :call FzyCommand("find . -type f", ":vs")
90 | nnoremap s :call FzyCommand("find . -type f", ":sp")
91 | ```
92 |
93 | Any program can be used to filter files presented through fzy. [ag (the silver searcher)](https://github.com/ggreer/the_silver_searcher) can be used to ignore files specified by `.gitignore`.
94 |
95 | ``` vim
96 | nnoremap e :call FzyCommand("ag . --silent -l -g ''", ":e")
97 | nnoremap v :call FzyCommand("ag . --silent -l -g ''", ":vs")
98 | nnoremap s :call FzyCommand("ag . --silent -l -g ''", ":sp")
99 | ```
100 |
101 | ## Sorting
102 |
103 | fzy attempts to present the best matches first. The following considerations are weighted when sorting:
104 |
105 | It prefers consecutive characters: `file` will match file over filter.
106 |
107 | It prefers matching the beginning of words: `amp` is likely to match app/models/posts.rb.
108 |
109 | It prefers shorter matches: `abce` matches abcdef over abc de.
110 |
111 | It prefers shorter candidates: `test` matches tests over testing.
112 |
113 | ## See Also
114 |
115 | * [fzy.js](https://github.com/jhawthorn/fzy.js) Javascript port
116 |
117 |
118 |
--------------------------------------------------------------------------------
/contrib/fzy-dvtm:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | _echo() {
4 | printf %s\\n "$*"
5 | }
6 |
7 | fatal() {
8 | _echo "$*" >&2
9 | exit 1
10 | }
11 |
12 | main() {
13 | if [ -z "${DVTM_CMD_FIFO}" ]; then
14 | fatal "No DVTM_CMD_FIFO variable detected in the environment"
15 | fi
16 |
17 | readonly PATH_DIR_TMP=$(mktemp -d)
18 | readonly PATH_FIFO_IN="${PATH_DIR_TMP}/in"
19 | readonly PATH_FIFO_OUT="${PATH_DIR_TMP}/out"
20 | readonly PATH_FIFO_RET="${PATH_DIR_TMP}/ret"
21 |
22 | if [ -z "${PATH_DIR_TMP}" ]; then
23 | fatal "Unable to create a temporary directory"
24 | fi
25 |
26 | args=""
27 | for i in "$@"; do
28 | if [ -z "${args}" ]; then
29 | args="\\'${i}\\'"
30 | else
31 | args="${args} \\'${i}\\'"
32 | fi
33 | done
34 |
35 | mkfifo "${PATH_FIFO_IN}" "${PATH_FIFO_OUT}"
36 |
37 | _echo \
38 | "create 'fzy ${args} < \\'${PATH_FIFO_IN}\\' > \\'${PATH_FIFO_OUT}\\' 2>&1; echo $? > \\'${PATH_FIFO_RET}\\''" \
39 | > "${DVTM_CMD_FIFO}"
40 | cat <&0 > "${PATH_FIFO_IN}" &
41 | cat < "${PATH_FIFO_OUT}"
42 |
43 | readonly CODE_RET=$(head -n 1 "${PATH_FIFO_RET}")
44 |
45 | rm -rf "${PATH_DIR_TMP}"
46 |
47 | exit "${CODE_RET}"
48 | }
49 |
50 | main "$@"
51 |
--------------------------------------------------------------------------------
/contrib/fzy-tmux:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | _echo() {
4 | printf %s\\n "$*"
5 | }
6 |
7 | fatal() {
8 | _echo "$*" >&2
9 | exit 1
10 | }
11 |
12 | main() {
13 | if [ -z "${TMUX}" ]; then
14 | fatal "No TMUX variable detected in the environment"
15 | fi
16 |
17 | readonly PATH_DIR_TMP=$(mktemp -d)
18 | readonly PATH_FIFO_IN="${PATH_DIR_TMP}/in"
19 | readonly PATH_FIFO_OUT="${PATH_DIR_TMP}/out"
20 | readonly PATH_FIFO_RET="${PATH_DIR_TMP}/ret"
21 |
22 | if [ -z "${PATH_DIR_TMP}" ]; then
23 | fatal "Unable to create a temporary directory"
24 | fi
25 |
26 | mkfifo "${PATH_FIFO_IN}" "${PATH_FIFO_OUT}" "${PATH_FIFO_RET}"
27 |
28 | export TMUX=$(_echo "${TMUX}" | cut -d , -f 1,2)
29 | eval "tmux \
30 | set-window-option synchronize-panes off \\; \
31 | set-window-option remain-on-exit off \\; \
32 | split-window \"fzy $* < '${PATH_FIFO_IN}' > '${PATH_FIFO_OUT}' 2>&1; echo $? > '${PATH_FIFO_RET}'\""
33 |
34 | cat <&0 > "${PATH_FIFO_IN}" &
35 | cat < "${PATH_FIFO_OUT}"
36 |
37 | readonly CODE_RET=$(head -n 1 "${PATH_FIFO_RET}")
38 |
39 | rm -rf "${PATH_DIR_TMP}"
40 |
41 | exit "${CODE_RET}"
42 | }
43 |
44 | main "$@"
45 |
--------------------------------------------------------------------------------
/deps/greatest/greatest.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011-2017 Scott Vokes
3 | *
4 | * Permission to use, copy, modify, and/or distribute this software for any
5 | * purpose with or without fee is hereby granted, provided that the above
6 | * copyright notice and this permission notice appear in all copies.
7 | *
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 | */
16 |
17 | #ifndef GREATEST_H
18 | #define GREATEST_H
19 |
20 | #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS)
21 | extern "C" {
22 | #endif
23 |
24 | /* 1.3.1 */
25 | #define GREATEST_VERSION_MAJOR 1
26 | #define GREATEST_VERSION_MINOR 3
27 | #define GREATEST_VERSION_PATCH 1
28 |
29 | /* A unit testing system for C, contained in 1 file.
30 | * It doesn't use dynamic allocation or depend on anything
31 | * beyond ANSI C89.
32 | *
33 | * An up-to-date version can be found at:
34 | * https://github.com/silentbicycle/greatest/
35 | */
36 |
37 |
38 | /*********************************************************************
39 | * Minimal test runner template
40 | *********************************************************************/
41 | #if 0
42 |
43 | #include "greatest.h"
44 |
45 | TEST foo_should_foo(void) {
46 | PASS();
47 | }
48 |
49 | static void setup_cb(void *data) {
50 | printf("setup callback for each test case\n");
51 | }
52 |
53 | static void teardown_cb(void *data) {
54 | printf("teardown callback for each test case\n");
55 | }
56 |
57 | SUITE(suite) {
58 | /* Optional setup/teardown callbacks which will be run before/after
59 | * every test case. If using a test suite, they will be cleared when
60 | * the suite finishes. */
61 | SET_SETUP(setup_cb, voidp_to_callback_data);
62 | SET_TEARDOWN(teardown_cb, voidp_to_callback_data);
63 |
64 | RUN_TEST(foo_should_foo);
65 | }
66 |
67 | /* Add definitions that need to be in the test runner's main file. */
68 | GREATEST_MAIN_DEFS();
69 |
70 | /* Set up, run suite(s) of tests, report pass/fail/skip stats. */
71 | int run_tests(void) {
72 | GREATEST_INIT(); /* init. greatest internals */
73 | /* List of suites to run (if any). */
74 | RUN_SUITE(suite);
75 |
76 | /* Tests can also be run directly, without using test suites. */
77 | RUN_TEST(foo_should_foo);
78 |
79 | GREATEST_PRINT_REPORT(); /* display results */
80 | return greatest_all_passed();
81 | }
82 |
83 | /* main(), for a standalone command-line test runner.
84 | * This replaces run_tests above, and adds command line option
85 | * handling and exiting with a pass/fail status. */
86 | int main(int argc, char **argv) {
87 | GREATEST_MAIN_BEGIN(); /* init & parse command-line args */
88 | RUN_SUITE(suite);
89 | GREATEST_MAIN_END(); /* display results */
90 | }
91 |
92 | #endif
93 | /*********************************************************************/
94 |
95 |
96 | #include
97 | #include
98 | #include
99 | #include
100 |
101 | /***********
102 | * Options *
103 | ***********/
104 |
105 | /* Default column width for non-verbose output. */
106 | #ifndef GREATEST_DEFAULT_WIDTH
107 | #define GREATEST_DEFAULT_WIDTH 72
108 | #endif
109 |
110 | /* FILE *, for test logging. */
111 | #ifndef GREATEST_STDOUT
112 | #define GREATEST_STDOUT stdout
113 | #endif
114 |
115 | /* Remove GREATEST_ prefix from most commonly used symbols? */
116 | #ifndef GREATEST_USE_ABBREVS
117 | #define GREATEST_USE_ABBREVS 1
118 | #endif
119 |
120 | /* Set to 0 to disable all use of setjmp/longjmp. */
121 | #ifndef GREATEST_USE_LONGJMP
122 | #define GREATEST_USE_LONGJMP 1
123 | #endif
124 |
125 | /* Make it possible to replace fprintf with another
126 | * function with the same interface. */
127 | #ifndef GREATEST_FPRINTF
128 | #define GREATEST_FPRINTF fprintf
129 | #endif
130 |
131 | #if GREATEST_USE_LONGJMP
132 | #include
133 | #endif
134 |
135 | /* Set to 0 to disable all use of time.h / clock(). */
136 | #ifndef GREATEST_USE_TIME
137 | #define GREATEST_USE_TIME 1
138 | #endif
139 |
140 | #if GREATEST_USE_TIME
141 | #include
142 | #endif
143 |
144 | /* Floating point type, for ASSERT_IN_RANGE. */
145 | #ifndef GREATEST_FLOAT
146 | #define GREATEST_FLOAT double
147 | #define GREATEST_FLOAT_FMT "%g"
148 | #endif
149 |
150 |
151 | /*********
152 | * Types *
153 | *********/
154 |
155 | /* Info for the current running suite. */
156 | typedef struct greatest_suite_info {
157 | unsigned int tests_run;
158 | unsigned int passed;
159 | unsigned int failed;
160 | unsigned int skipped;
161 |
162 | #if GREATEST_USE_TIME
163 | /* timers, pre/post running suite and individual tests */
164 | clock_t pre_suite;
165 | clock_t post_suite;
166 | clock_t pre_test;
167 | clock_t post_test;
168 | #endif
169 | } greatest_suite_info;
170 |
171 | /* Type for a suite function. */
172 | typedef void greatest_suite_cb(void);
173 |
174 | /* Types for setup/teardown callbacks. If non-NULL, these will be run
175 | * and passed the pointer to their additional data. */
176 | typedef void greatest_setup_cb(void *udata);
177 | typedef void greatest_teardown_cb(void *udata);
178 |
179 | /* Type for an equality comparison between two pointers of the same type.
180 | * Should return non-0 if equal, otherwise 0.
181 | * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */
182 | typedef int greatest_equal_cb(const void *exp, const void *got, void *udata);
183 |
184 | /* Type for a callback that prints a value pointed to by T.
185 | * Return value has the same meaning as printf's.
186 | * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */
187 | typedef int greatest_printf_cb(const void *t, void *udata);
188 |
189 | /* Callbacks for an arbitrary type; needed for type-specific
190 | * comparisons via GREATEST_ASSERT_EQUAL_T[m].*/
191 | typedef struct greatest_type_info {
192 | greatest_equal_cb *equal;
193 | greatest_printf_cb *print;
194 | } greatest_type_info;
195 |
196 | typedef struct greatest_memory_cmp_env {
197 | const unsigned char *exp;
198 | const unsigned char *got;
199 | size_t size;
200 | } greatest_memory_cmp_env;
201 |
202 | /* Callbacks for string and raw memory types. */
203 | extern greatest_type_info greatest_type_info_string;
204 | extern greatest_type_info greatest_type_info_memory;
205 |
206 | typedef enum {
207 | GREATEST_FLAG_FIRST_FAIL = 0x01,
208 | GREATEST_FLAG_LIST_ONLY = 0x02
209 | } greatest_flag_t;
210 |
211 | /* Internal state for a PRNG, used to shuffle test order. */
212 | struct greatest_prng {
213 | unsigned char random_order; /* use random ordering? */
214 | unsigned char initialized; /* is random ordering initialized? */
215 | unsigned char pad_0[2];
216 | unsigned long state; /* PRNG state */
217 | unsigned long count; /* how many tests, this pass */
218 | unsigned long count_ceil; /* total number of tests */
219 | unsigned long count_run; /* total tests run */
220 | unsigned long mod; /* power-of-2 ceiling of count_ceil */
221 | unsigned long a; /* LCG multiplier */
222 | unsigned long c; /* LCG increment */
223 | };
224 |
225 | /* Struct containing all test runner state. */
226 | typedef struct greatest_run_info {
227 | unsigned char flags;
228 | unsigned char verbosity;
229 | unsigned char pad_0[2];
230 |
231 | unsigned int tests_run; /* total test count */
232 |
233 | /* currently running test suite */
234 | greatest_suite_info suite;
235 |
236 | /* overall pass/fail/skip counts */
237 | unsigned int passed;
238 | unsigned int failed;
239 | unsigned int skipped;
240 | unsigned int assertions;
241 |
242 | /* info to print about the most recent failure */
243 | unsigned int fail_line;
244 | unsigned int pad_1;
245 | const char *fail_file;
246 | const char *msg;
247 |
248 | /* current setup/teardown hooks and userdata */
249 | greatest_setup_cb *setup;
250 | void *setup_udata;
251 | greatest_teardown_cb *teardown;
252 | void *teardown_udata;
253 |
254 | /* formatting info for ".....s...F"-style output */
255 | unsigned int col;
256 | unsigned int width;
257 |
258 | /* only run a specific suite or test */
259 | const char *suite_filter;
260 | const char *test_filter;
261 | const char *test_exclude;
262 |
263 | struct greatest_prng prng[2]; /* 0: suites, 1: tests */
264 |
265 | #if GREATEST_USE_TIME
266 | /* overall timers */
267 | clock_t begin;
268 | clock_t end;
269 | #endif
270 |
271 | #if GREATEST_USE_LONGJMP
272 | int pad_jmp_buf;
273 | jmp_buf jump_dest;
274 | #endif
275 | } greatest_run_info;
276 |
277 | struct greatest_report_t {
278 | /* overall pass/fail/skip counts */
279 | unsigned int passed;
280 | unsigned int failed;
281 | unsigned int skipped;
282 | unsigned int assertions;
283 | };
284 |
285 | /* Global var for the current testing context.
286 | * Initialized by GREATEST_MAIN_DEFS(). */
287 | extern greatest_run_info greatest_info;
288 |
289 | /* Type for ASSERT_ENUM_EQ's ENUM_STR argument. */
290 | typedef const char *greatest_enum_str_fun(int value);
291 |
292 | /**********************
293 | * Exported functions *
294 | **********************/
295 |
296 | /* These are used internally by greatest. */
297 | void greatest_do_pass(const char *name);
298 | void greatest_do_fail(const char *name);
299 | void greatest_do_skip(const char *name);
300 | int greatest_suite_pre(const char *suite_name);
301 | void greatest_suite_post(void);
302 | int greatest_test_pre(const char *name);
303 | void greatest_test_post(const char *name, int res);
304 | void greatest_usage(const char *name);
305 | int greatest_do_assert_equal_t(const void *exp, const void *got,
306 | greatest_type_info *type_info, void *udata);
307 | void greatest_prng_init_first_pass(int id);
308 | int greatest_prng_init_second_pass(int id, unsigned long seed);
309 | void greatest_prng_step(int id);
310 |
311 | /* These are part of the public greatest API. */
312 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata);
313 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata);
314 | int greatest_all_passed(void);
315 | void greatest_set_suite_filter(const char *filter);
316 | void greatest_set_test_filter(const char *filter);
317 | void greatest_set_test_exclude(const char *filter);
318 | void greatest_stop_at_first_fail(void);
319 | void greatest_get_report(struct greatest_report_t *report);
320 | unsigned int greatest_get_verbosity(void);
321 | void greatest_set_verbosity(unsigned int verbosity);
322 | void greatest_set_flag(greatest_flag_t flag);
323 |
324 |
325 | /********************
326 | * Language Support *
327 | ********************/
328 |
329 | /* If __VA_ARGS__ (C99) is supported, allow parametric testing
330 | * without needing to manually manage the argument struct. */
331 | #if __STDC_VERSION__ >= 19901L || _MSC_VER >= 1800
332 | #define GREATEST_VA_ARGS
333 | #endif
334 |
335 |
336 | /**********
337 | * Macros *
338 | **********/
339 |
340 | /* Define a suite. */
341 | #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void)
342 |
343 | /* Declare a suite, provided by another compilation unit. */
344 | #define GREATEST_SUITE_EXTERN(NAME) void NAME(void)
345 |
346 | /* Start defining a test function.
347 | * The arguments are not included, to allow parametric testing. */
348 | #define GREATEST_TEST static enum greatest_test_res
349 |
350 | /* PASS/FAIL/SKIP result from a test. Used internally. */
351 | typedef enum greatest_test_res {
352 | GREATEST_TEST_RES_PASS = 0,
353 | GREATEST_TEST_RES_FAIL = -1,
354 | GREATEST_TEST_RES_SKIP = 1
355 | } greatest_test_res;
356 |
357 | /* Run a suite. */
358 | #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME)
359 |
360 | /* Run a test in the current suite. */
361 | #define GREATEST_RUN_TEST(TEST) \
362 | do { \
363 | if (greatest_test_pre(#TEST) == 1) { \
364 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \
365 | if (res == GREATEST_TEST_RES_PASS) { \
366 | res = TEST(); \
367 | } \
368 | greatest_test_post(#TEST, res); \
369 | } \
370 | } while (0)
371 |
372 | /* Ignore a test, don't warn about it being unused. */
373 | #define GREATEST_IGNORE_TEST(TEST) (void)TEST
374 |
375 | /* Run a test in the current suite with one void * argument,
376 | * which can be a pointer to a struct with multiple arguments. */
377 | #define GREATEST_RUN_TEST1(TEST, ENV) \
378 | do { \
379 | if (greatest_test_pre(#TEST) == 1) { \
380 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \
381 | if (res == GREATEST_TEST_RES_PASS) { \
382 | res = TEST(ENV); \
383 | } \
384 | greatest_test_post(#TEST, res); \
385 | } \
386 | } while (0)
387 |
388 | #ifdef GREATEST_VA_ARGS
389 | #define GREATEST_RUN_TESTp(TEST, ...) \
390 | do { \
391 | if (greatest_test_pre(#TEST) == 1) { \
392 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \
393 | if (res == GREATEST_TEST_RES_PASS) { \
394 | res = TEST(__VA_ARGS__); \
395 | } \
396 | greatest_test_post(#TEST, res); \
397 | } \
398 | } while (0)
399 | #endif
400 |
401 |
402 | /* Check if the test runner is in verbose mode. */
403 | #define GREATEST_IS_VERBOSE() ((greatest_info.verbosity) > 0)
404 | #define GREATEST_LIST_ONLY() \
405 | (greatest_info.flags & GREATEST_FLAG_LIST_ONLY)
406 | #define GREATEST_FIRST_FAIL() \
407 | (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL)
408 | #define GREATEST_FAILURE_ABORT() \
409 | (GREATEST_FIRST_FAIL() && \
410 | (greatest_info.suite.failed > 0 || greatest_info.failed > 0))
411 |
412 | /* Message-less forms of tests defined below. */
413 | #define GREATEST_PASS() GREATEST_PASSm(NULL)
414 | #define GREATEST_FAIL() GREATEST_FAILm(NULL)
415 | #define GREATEST_SKIP() GREATEST_SKIPm(NULL)
416 | #define GREATEST_ASSERT(COND) \
417 | GREATEST_ASSERTm(#COND, COND)
418 | #define GREATEST_ASSERT_OR_LONGJMP(COND) \
419 | GREATEST_ASSERT_OR_LONGJMPm(#COND, COND)
420 | #define GREATEST_ASSERT_FALSE(COND) \
421 | GREATEST_ASSERT_FALSEm(#COND, COND)
422 | #define GREATEST_ASSERT_EQ(EXP, GOT) \
423 | GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT)
424 | #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \
425 | GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT)
426 | #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \
427 | GREATEST_ASSERT_IN_RANGEm(#EXP " != " #GOT " +/- " #TOL, EXP, GOT, TOL)
428 | #define GREATEST_ASSERT_EQUAL_T(EXP, GOT, TYPE_INFO, UDATA) \
429 | GREATEST_ASSERT_EQUAL_Tm(#EXP " != " #GOT, EXP, GOT, TYPE_INFO, UDATA)
430 | #define GREATEST_ASSERT_STR_EQ(EXP, GOT) \
431 | GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT)
432 | #define GREATEST_ASSERT_STRN_EQ(EXP, GOT, SIZE) \
433 | GREATEST_ASSERT_STRN_EQm(#EXP " != " #GOT, EXP, GOT, SIZE)
434 | #define GREATEST_ASSERT_MEM_EQ(EXP, GOT, SIZE) \
435 | GREATEST_ASSERT_MEM_EQm(#EXP " != " #GOT, EXP, GOT, SIZE)
436 | #define GREATEST_ASSERT_ENUM_EQ(EXP, GOT, ENUM_STR) \
437 | GREATEST_ASSERT_ENUM_EQm(#EXP " != " #GOT, EXP, GOT, ENUM_STR)
438 |
439 | /* The following forms take an additional message argument first,
440 | * to be displayed by the test runner. */
441 |
442 | /* Fail if a condition is not true, with message. */
443 | #define GREATEST_ASSERTm(MSG, COND) \
444 | do { \
445 | greatest_info.assertions++; \
446 | if (!(COND)) { GREATEST_FAILm(MSG); } \
447 | } while (0)
448 |
449 | /* Fail if a condition is not true, longjmping out of test. */
450 | #define GREATEST_ASSERT_OR_LONGJMPm(MSG, COND) \
451 | do { \
452 | greatest_info.assertions++; \
453 | if (!(COND)) { GREATEST_FAIL_WITH_LONGJMPm(MSG); } \
454 | } while (0)
455 |
456 | /* Fail if a condition is not false, with message. */
457 | #define GREATEST_ASSERT_FALSEm(MSG, COND) \
458 | do { \
459 | greatest_info.assertions++; \
460 | if ((COND)) { GREATEST_FAILm(MSG); } \
461 | } while (0)
462 |
463 | /* Fail if EXP != GOT (equality comparison by ==). */
464 | #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \
465 | do { \
466 | greatest_info.assertions++; \
467 | if ((EXP) != (GOT)) { GREATEST_FAILm(MSG); } \
468 | } while (0)
469 |
470 | /* Fail if EXP != GOT (equality comparison by ==).
471 | * Warning: FMT, EXP, and GOT will be evaluated more
472 | * than once on failure. */
473 | #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \
474 | do { \
475 | greatest_info.assertions++; \
476 | if ((EXP) != (GOT)) { \
477 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \
478 | GREATEST_FPRINTF(GREATEST_STDOUT, FMT, EXP); \
479 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \
480 | GREATEST_FPRINTF(GREATEST_STDOUT, FMT, GOT); \
481 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \
482 | GREATEST_FAILm(MSG); \
483 | } \
484 | } while (0)
485 |
486 | /* Fail if EXP is not equal to GOT, printing enum IDs. */
487 | #define GREATEST_ASSERT_ENUM_EQm(MSG, EXP, GOT, ENUM_STR) \
488 | do { \
489 | int greatest_EXP = (int)(EXP); \
490 | int greatest_GOT = (int)(GOT); \
491 | greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \
492 | if (greatest_EXP != greatest_GOT) { \
493 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: %s", \
494 | greatest_ENUM_STR(greatest_EXP)); \
495 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: %s\n", \
496 | greatest_ENUM_STR(greatest_GOT)); \
497 | GREATEST_FAILm(MSG); \
498 | } \
499 | } while (0) \
500 |
501 | /* Fail if GOT not in range of EXP +|- TOL. */
502 | #define GREATEST_ASSERT_IN_RANGEm(MSG, EXP, GOT, TOL) \
503 | do { \
504 | GREATEST_FLOAT greatest_EXP = (EXP); \
505 | GREATEST_FLOAT greatest_GOT = (GOT); \
506 | GREATEST_FLOAT greatest_TOL = (TOL); \
507 | greatest_info.assertions++; \
508 | if ((greatest_EXP > greatest_GOT && \
509 | greatest_EXP - greatest_GOT > greatest_TOL) || \
510 | (greatest_EXP < greatest_GOT && \
511 | greatest_GOT - greatest_EXP > greatest_TOL)) { \
512 | GREATEST_FPRINTF(GREATEST_STDOUT, \
513 | "\nExpected: " GREATEST_FLOAT_FMT \
514 | " +/- " GREATEST_FLOAT_FMT \
515 | "\n Got: " GREATEST_FLOAT_FMT \
516 | "\n", \
517 | greatest_EXP, greatest_TOL, greatest_GOT); \
518 | GREATEST_FAILm(MSG); \
519 | } \
520 | } while (0)
521 |
522 | /* Fail if EXP is not equal to GOT, according to strcmp. */
523 | #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \
524 | do { \
525 | GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \
526 | &greatest_type_info_string, NULL); \
527 | } while (0) \
528 |
529 | /* Fail if EXP is not equal to GOT, according to strcmp. */
530 | #define GREATEST_ASSERT_STRN_EQm(MSG, EXP, GOT, SIZE) \
531 | do { \
532 | size_t size = SIZE; \
533 | GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \
534 | &greatest_type_info_string, &size); \
535 | } while (0) \
536 |
537 | /* Fail if EXP is not equal to GOT, according to memcmp. */
538 | #define GREATEST_ASSERT_MEM_EQm(MSG, EXP, GOT, SIZE) \
539 | do { \
540 | greatest_memory_cmp_env env; \
541 | env.exp = (const unsigned char *)EXP; \
542 | env.got = (const unsigned char *)GOT; \
543 | env.size = SIZE; \
544 | GREATEST_ASSERT_EQUAL_Tm(MSG, env.exp, env.got, \
545 | &greatest_type_info_memory, &env); \
546 | } while (0) \
547 |
548 | /* Fail if EXP is not equal to GOT, according to a comparison
549 | * callback in TYPE_INFO. If they are not equal, optionally use a
550 | * print callback in TYPE_INFO to print them. */
551 | #define GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, TYPE_INFO, UDATA) \
552 | do { \
553 | greatest_type_info *type_info = (TYPE_INFO); \
554 | greatest_info.assertions++; \
555 | if (!greatest_do_assert_equal_t(EXP, GOT, \
556 | type_info, UDATA)) { \
557 | if (type_info == NULL || type_info->equal == NULL) { \
558 | GREATEST_FAILm("type_info->equal callback missing!"); \
559 | } else { \
560 | GREATEST_FAILm(MSG); \
561 | } \
562 | } \
563 | } while (0) \
564 |
565 | /* Pass. */
566 | #define GREATEST_PASSm(MSG) \
567 | do { \
568 | greatest_info.msg = MSG; \
569 | return GREATEST_TEST_RES_PASS; \
570 | } while (0)
571 |
572 | /* Fail. */
573 | #define GREATEST_FAILm(MSG) \
574 | do { \
575 | greatest_info.fail_file = __FILE__; \
576 | greatest_info.fail_line = __LINE__; \
577 | greatest_info.msg = MSG; \
578 | return GREATEST_TEST_RES_FAIL; \
579 | } while (0)
580 |
581 | /* Optional GREATEST_FAILm variant that longjmps. */
582 | #if GREATEST_USE_LONGJMP
583 | #define GREATEST_FAIL_WITH_LONGJMP() GREATEST_FAIL_WITH_LONGJMPm(NULL)
584 | #define GREATEST_FAIL_WITH_LONGJMPm(MSG) \
585 | do { \
586 | greatest_info.fail_file = __FILE__; \
587 | greatest_info.fail_line = __LINE__; \
588 | greatest_info.msg = MSG; \
589 | longjmp(greatest_info.jump_dest, GREATEST_TEST_RES_FAIL); \
590 | } while (0)
591 | #endif
592 |
593 | /* Skip the current test. */
594 | #define GREATEST_SKIPm(MSG) \
595 | do { \
596 | greatest_info.msg = MSG; \
597 | return GREATEST_TEST_RES_SKIP; \
598 | } while (0)
599 |
600 | /* Check the result of a subfunction using ASSERT, etc. */
601 | #define GREATEST_CHECK_CALL(RES) \
602 | do { \
603 | enum greatest_test_res greatest_RES = RES; \
604 | if (greatest_RES != GREATEST_TEST_RES_PASS) { \
605 | return greatest_RES; \
606 | } \
607 | } while (0) \
608 |
609 | #if GREATEST_USE_TIME
610 | #define GREATEST_SET_TIME(NAME) \
611 | NAME = clock(); \
612 | if (NAME == (clock_t) -1) { \
613 | GREATEST_FPRINTF(GREATEST_STDOUT, \
614 | "clock error: %s\n", #NAME); \
615 | exit(EXIT_FAILURE); \
616 | }
617 |
618 | #define GREATEST_CLOCK_DIFF(C1, C2) \
619 | GREATEST_FPRINTF(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \
620 | (long unsigned int) (C2) - (long unsigned int)(C1), \
621 | (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC))
622 | #else
623 | #define GREATEST_SET_TIME(UNUSED)
624 | #define GREATEST_CLOCK_DIFF(UNUSED1, UNUSED2)
625 | #endif
626 |
627 | #if GREATEST_USE_LONGJMP
628 | #define GREATEST_SAVE_CONTEXT() \
629 | /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call * \
630 | * so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \
631 | ((enum greatest_test_res)(setjmp(greatest_info.jump_dest)))
632 | #else
633 | #define GREATEST_SAVE_CONTEXT() \
634 | /*a no-op, since setjmp/longjmp aren't being used */ \
635 | GREATEST_TEST_RES_PASS
636 | #endif
637 |
638 | /* Run every suite / test function run within BODY in pseudo-random
639 | * order, seeded by SEED. (The top 3 bits of the seed are ignored.)
640 | *
641 | * This should be called like:
642 | * GREATEST_SHUFFLE_TESTS(seed, {
643 | * GREATEST_RUN_TEST(some_test);
644 | * GREATEST_RUN_TEST(some_other_test);
645 | * GREATEST_RUN_TEST(yet_another_test);
646 | * });
647 | *
648 | * Note that the body of the second argument will be evaluated
649 | * multiple times. */
650 | #define GREATEST_SHUFFLE_SUITES(SD, BODY) GREATEST_SHUFFLE(0, SD, BODY)
651 | #define GREATEST_SHUFFLE_TESTS(SD, BODY) GREATEST_SHUFFLE(1, SD, BODY)
652 | #define GREATEST_SHUFFLE(ID, SD, BODY) \
653 | do { \
654 | struct greatest_prng *prng = &greatest_info.prng[ID]; \
655 | greatest_prng_init_first_pass(ID); \
656 | do { \
657 | prng->count = 0; \
658 | if (prng->initialized) { greatest_prng_step(ID); } \
659 | BODY; \
660 | if (!prng->initialized) { \
661 | if (!greatest_prng_init_second_pass(ID, SD)) { break; } \
662 | } else if (prng->count_run == prng->count_ceil) { \
663 | break; \
664 | } \
665 | } while (!GREATEST_FAILURE_ABORT()); \
666 | prng->count_run = prng->random_order = prng->initialized = 0; \
667 | } while(0)
668 |
669 | /* Include several function definitions in the main test file. */
670 | #define GREATEST_MAIN_DEFS() \
671 | \
672 | /* Is FILTER a subset of NAME? */ \
673 | static int greatest_name_match(const char *name, const char *filter, \
674 | int res_if_none) { \
675 | size_t offset = 0; \
676 | size_t filter_len = filter ? strlen(filter) : 0; \
677 | if (filter_len == 0) { return res_if_none; } /* no filter */ \
678 | while (name[offset] != '\0') { \
679 | if (name[offset] == filter[0]) { \
680 | if (0 == strncmp(&name[offset], filter, filter_len)) { \
681 | return 1; \
682 | } \
683 | } \
684 | offset++; \
685 | } \
686 | \
687 | return 0; \
688 | } \
689 | \
690 | /* Before running a test, check the name filtering and \
691 | * test shuffling state, if applicable, and then call setup hooks. */ \
692 | int greatest_test_pre(const char *name) { \
693 | struct greatest_run_info *g = &greatest_info; \
694 | int match = greatest_name_match(name, g->test_filter, 1) && \
695 | !greatest_name_match(name, g->test_exclude, 0); \
696 | if (GREATEST_LIST_ONLY()) { /* just listing test names */ \
697 | if (match) { fprintf(GREATEST_STDOUT, " %s\n", name); } \
698 | return 0; \
699 | } \
700 | if (match && (!GREATEST_FIRST_FAIL() || g->suite.failed == 0)) { \
701 | struct greatest_prng *p = &g->prng[1]; \
702 | if (p->random_order) { \
703 | p->count++; \
704 | if (!p->initialized || ((p->count - 1) != p->state)) { \
705 | return 0; /* don't run this test yet */ \
706 | } \
707 | } \
708 | GREATEST_SET_TIME(g->suite.pre_test); \
709 | if (g->setup) { g->setup(g->setup_udata); } \
710 | p->count_run++; \
711 | return 1; /* test should be run */ \
712 | } else { \
713 | return 0; /* skipped */ \
714 | } \
715 | } \
716 | \
717 | void greatest_test_post(const char *name, int res) { \
718 | GREATEST_SET_TIME(greatest_info.suite.post_test); \
719 | if (greatest_info.teardown) { \
720 | void *udata = greatest_info.teardown_udata; \
721 | greatest_info.teardown(udata); \
722 | } \
723 | \
724 | if (res <= GREATEST_TEST_RES_FAIL) { \
725 | greatest_do_fail(name); \
726 | } else if (res >= GREATEST_TEST_RES_SKIP) { \
727 | greatest_do_skip(name); \
728 | } else if (res == GREATEST_TEST_RES_PASS) { \
729 | greatest_do_pass(name); \
730 | } \
731 | greatest_info.suite.tests_run++; \
732 | greatest_info.col++; \
733 | if (GREATEST_IS_VERBOSE()) { \
734 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \
735 | greatest_info.suite.post_test); \
736 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \
737 | } else if (greatest_info.col % greatest_info.width == 0) { \
738 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \
739 | greatest_info.col = 0; \
740 | } \
741 | fflush(GREATEST_STDOUT); \
742 | } \
743 | \
744 | static void report_suite(void) { \
745 | if (greatest_info.suite.tests_run > 0) { \
746 | GREATEST_FPRINTF(GREATEST_STDOUT, \
747 | "\n%u test%s - %u passed, %u failed, %u skipped", \
748 | greatest_info.suite.tests_run, \
749 | greatest_info.suite.tests_run == 1 ? "" : "s", \
750 | greatest_info.suite.passed, \
751 | greatest_info.suite.failed, \
752 | greatest_info.suite.skipped); \
753 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \
754 | greatest_info.suite.post_suite); \
755 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \
756 | } \
757 | } \
758 | \
759 | static void update_counts_and_reset_suite(void) { \
760 | greatest_info.setup = NULL; \
761 | greatest_info.setup_udata = NULL; \
762 | greatest_info.teardown = NULL; \
763 | greatest_info.teardown_udata = NULL; \
764 | greatest_info.passed += greatest_info.suite.passed; \
765 | greatest_info.failed += greatest_info.suite.failed; \
766 | greatest_info.skipped += greatest_info.suite.skipped; \
767 | greatest_info.tests_run += greatest_info.suite.tests_run; \
768 | memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \
769 | greatest_info.col = 0; \
770 | } \
771 | \
772 | int greatest_suite_pre(const char *suite_name) { \
773 | struct greatest_prng *p = &greatest_info.prng[0]; \
774 | if (!greatest_name_match(suite_name, greatest_info.suite_filter, 1) \
775 | || (GREATEST_FIRST_FAIL() && greatest_info.failed > 0)) { \
776 | return 0; \
777 | } \
778 | if (p->random_order) { \
779 | p->count++; \
780 | if (!p->initialized || ((p->count - 1) != p->state)) { \
781 | return 0; /* don't run this suite yet */ \
782 | } \
783 | } \
784 | p->count_run++; \
785 | update_counts_and_reset_suite(); \
786 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \
787 | GREATEST_SET_TIME(greatest_info.suite.pre_suite); \
788 | return 1; \
789 | } \
790 | \
791 | void greatest_suite_post(void) { \
792 | GREATEST_SET_TIME(greatest_info.suite.post_suite); \
793 | report_suite(); \
794 | } \
795 | \
796 | static void greatest_run_suite(greatest_suite_cb *suite_cb, \
797 | const char *suite_name) { \
798 | if (greatest_suite_pre(suite_name)) { \
799 | suite_cb(); \
800 | greatest_suite_post(); \
801 | } \
802 | } \
803 | \
804 | void greatest_do_pass(const char *name) { \
805 | if (GREATEST_IS_VERBOSE()) { \
806 | GREATEST_FPRINTF(GREATEST_STDOUT, "PASS %s: %s", \
807 | name, greatest_info.msg ? greatest_info.msg : ""); \
808 | } else { \
809 | GREATEST_FPRINTF(GREATEST_STDOUT, "."); \
810 | } \
811 | greatest_info.suite.passed++; \
812 | } \
813 | \
814 | void greatest_do_fail(const char *name) { \
815 | if (GREATEST_IS_VERBOSE()) { \
816 | GREATEST_FPRINTF(GREATEST_STDOUT, \
817 | "FAIL %s: %s (%s:%u)", \
818 | name, greatest_info.msg ? greatest_info.msg : "", \
819 | greatest_info.fail_file, greatest_info.fail_line); \
820 | } else { \
821 | GREATEST_FPRINTF(GREATEST_STDOUT, "F"); \
822 | greatest_info.col++; \
823 | /* add linebreak if in line of '.'s */ \
824 | if (greatest_info.col != 0) { \
825 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \
826 | greatest_info.col = 0; \
827 | } \
828 | GREATEST_FPRINTF(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \
829 | name, \
830 | greatest_info.msg ? greatest_info.msg : "", \
831 | greatest_info.fail_file, greatest_info.fail_line); \
832 | } \
833 | greatest_info.suite.failed++; \
834 | } \
835 | \
836 | void greatest_do_skip(const char *name) { \
837 | if (GREATEST_IS_VERBOSE()) { \
838 | GREATEST_FPRINTF(GREATEST_STDOUT, "SKIP %s: %s", \
839 | name, \
840 | greatest_info.msg ? \
841 | greatest_info.msg : "" ); \
842 | } else { \
843 | GREATEST_FPRINTF(GREATEST_STDOUT, "s"); \
844 | } \
845 | greatest_info.suite.skipped++; \
846 | } \
847 | \
848 | int greatest_do_assert_equal_t(const void *exp, const void *got, \
849 | greatest_type_info *type_info, void *udata) { \
850 | int eq = 0; \
851 | if (type_info == NULL || type_info->equal == NULL) { \
852 | return 0; \
853 | } \
854 | eq = type_info->equal(exp, got, udata); \
855 | if (!eq) { \
856 | if (type_info->print != NULL) { \
857 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \
858 | (void)type_info->print(exp, udata); \
859 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \
860 | (void)type_info->print(got, udata); \
861 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \
862 | } else { \
863 | GREATEST_FPRINTF(GREATEST_STDOUT, \
864 | "GREATEST_ASSERT_EQUAL_T failure at %s:%u\n", \
865 | greatest_info.fail_file, \
866 | greatest_info.fail_line); \
867 | } \
868 | } \
869 | return eq; \
870 | } \
871 | \
872 | void greatest_usage(const char *name) { \
873 | GREATEST_FPRINTF(GREATEST_STDOUT, \
874 | "Usage: %s [--help] [-hlfv] [-s SUITE] [-t TEST]\n" \
875 | " -h, --help print this Help\n" \
876 | " -l List suites and tests, then exit (dry run)\n" \
877 | " -f Stop runner after first failure\n" \
878 | " -v Verbose output\n" \
879 | " -s SUITE only run suites containing string SUITE\n" \
880 | " -t TEST only run tests containing string TEST\n" \
881 | " -x EXCLUDE exclude tests containing string EXCLUDE\n", \
882 | name); \
883 | } \
884 | \
885 | static void greatest_parse_options(int argc, char **argv) { \
886 | int i = 0; \
887 | for (i = 1; i < argc; i++) { \
888 | if (argv[i][0] == '-') { \
889 | char f = argv[i][1]; \
890 | if ((f == 's' || f == 't' || f == 'x') && argc <= i + 1) { \
891 | greatest_usage(argv[0]); exit(EXIT_FAILURE); \
892 | } \
893 | switch (f) { \
894 | case 's': /* suite name filter */ \
895 | greatest_set_suite_filter(argv[i + 1]); i++; break; \
896 | case 't': /* test name filter */ \
897 | greatest_set_test_filter(argv[i + 1]); i++; break; \
898 | case 'x': /* test name exclusion */ \
899 | greatest_set_test_exclude(argv[i + 1]); i++; break; \
900 | case 'f': /* first fail flag */ \
901 | greatest_stop_at_first_fail(); break; \
902 | case 'l': /* list only (dry run) */ \
903 | greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; break; \
904 | case 'v': /* first fail flag */ \
905 | greatest_info.verbosity++; break; \
906 | case 'h': /* help */ \
907 | greatest_usage(argv[0]); exit(EXIT_SUCCESS); \
908 | case '-': \
909 | if (0 == strncmp("--help", argv[i], 6)) { \
910 | greatest_usage(argv[0]); exit(EXIT_SUCCESS); \
911 | } else if (0 == strncmp("--", argv[i], 2)) { \
912 | return; /* ignore following arguments */ \
913 | } /* fall through */ \
914 | default: \
915 | GREATEST_FPRINTF(GREATEST_STDOUT, \
916 | "Unknown argument '%s'\n", argv[i]); \
917 | greatest_usage(argv[0]); \
918 | exit(EXIT_FAILURE); \
919 | } \
920 | } \
921 | } \
922 | } \
923 | \
924 | int greatest_all_passed(void) { return (greatest_info.failed == 0); } \
925 | \
926 | void greatest_set_test_filter(const char *filter) { \
927 | greatest_info.test_filter = filter; \
928 | } \
929 | \
930 | void greatest_set_test_exclude(const char *filter) { \
931 | greatest_info.test_exclude = filter; \
932 | } \
933 | \
934 | void greatest_set_suite_filter(const char *filter) { \
935 | greatest_info.suite_filter = filter; \
936 | } \
937 | \
938 | void greatest_stop_at_first_fail(void) { \
939 | greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \
940 | } \
941 | \
942 | void greatest_get_report(struct greatest_report_t *report) { \
943 | if (report) { \
944 | report->passed = greatest_info.passed; \
945 | report->failed = greatest_info.failed; \
946 | report->skipped = greatest_info.skipped; \
947 | report->assertions = greatest_info.assertions; \
948 | } \
949 | } \
950 | \
951 | unsigned int greatest_get_verbosity(void) { \
952 | return greatest_info.verbosity; \
953 | } \
954 | \
955 | void greatest_set_verbosity(unsigned int verbosity) { \
956 | greatest_info.verbosity = (unsigned char)verbosity; \
957 | } \
958 | \
959 | void greatest_set_flag(greatest_flag_t flag) { \
960 | greatest_info.flags |= flag; \
961 | } \
962 | \
963 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \
964 | greatest_info.setup = cb; \
965 | greatest_info.setup_udata = udata; \
966 | } \
967 | \
968 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \
969 | void *udata) { \
970 | greatest_info.teardown = cb; \
971 | greatest_info.teardown_udata = udata; \
972 | } \
973 | \
974 | static int greatest_string_equal_cb(const void *exp, const void *got, \
975 | void *udata) { \
976 | size_t *size = (size_t *)udata; \
977 | return (size != NULL \
978 | ? (0 == strncmp((const char *)exp, (const char *)got, *size)) \
979 | : (0 == strcmp((const char *)exp, (const char *)got))); \
980 | } \
981 | \
982 | static int greatest_string_printf_cb(const void *t, void *udata) { \
983 | (void)udata; /* note: does not check \0 termination. */ \
984 | return GREATEST_FPRINTF(GREATEST_STDOUT, "%s", (const char *)t); \
985 | } \
986 | \
987 | greatest_type_info greatest_type_info_string = { \
988 | greatest_string_equal_cb, \
989 | greatest_string_printf_cb, \
990 | }; \
991 | \
992 | static int greatest_memory_equal_cb(const void *exp, const void *got, \
993 | void *udata) { \
994 | greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \
995 | return (0 == memcmp(exp, got, env->size)); \
996 | } \
997 | \
998 | /* Hexdump raw memory, with differences highlighted */ \
999 | static int greatest_memory_printf_cb(const void *t, void *udata) { \
1000 | greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \
1001 | const unsigned char *buf = (const unsigned char *)t; \
1002 | unsigned char diff_mark = ' '; \
1003 | FILE *out = GREATEST_STDOUT; \
1004 | size_t i, line_i, line_len = 0; \
1005 | int len = 0; /* format hexdump with differences highlighted */ \
1006 | for (i = 0; i < env->size; i+= line_len) { \
1007 | diff_mark = ' '; \
1008 | line_len = env->size - i; \
1009 | if (line_len > 16) { line_len = 16; } \
1010 | for (line_i = i; line_i < i + line_len; line_i++) { \
1011 | if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \
1012 | } \
1013 | len += GREATEST_FPRINTF(out, "\n%04x %c ", \
1014 | (unsigned int)i, diff_mark); \
1015 | for (line_i = i; line_i < i + line_len; line_i++) { \
1016 | int m = env->exp[line_i] == env->got[line_i]; /* match? */ \
1017 | len += GREATEST_FPRINTF(out, "%02x%c", \
1018 | buf[line_i], m ? ' ' : '<'); \
1019 | } \
1020 | for (line_i = 0; line_i < 16 - line_len; line_i++) { \
1021 | len += GREATEST_FPRINTF(out, " "); \
1022 | } \
1023 | GREATEST_FPRINTF(out, " "); \
1024 | for (line_i = i; line_i < i + line_len; line_i++) { \
1025 | unsigned char c = buf[line_i]; \
1026 | len += GREATEST_FPRINTF(out, "%c", isprint(c) ? c : '.'); \
1027 | } \
1028 | } \
1029 | len += GREATEST_FPRINTF(out, "\n"); \
1030 | return len; \
1031 | } \
1032 | \
1033 | void greatest_prng_init_first_pass(int id) { \
1034 | greatest_info.prng[id].random_order = 1; \
1035 | greatest_info.prng[id].count_run = 0; \
1036 | } \
1037 | \
1038 | int greatest_prng_init_second_pass(int id, unsigned long seed) { \
1039 | static unsigned long primes[] = { 11, 101, 1009, 10007, \
1040 | 100003, 1000003, 10000019, 100000007, 1000000007, \
1041 | 1538461, 1865471, 17471, 2147483647 /* 2**32 - 1 */, }; \
1042 | struct greatest_prng *prng = &greatest_info.prng[id]; \
1043 | if (prng->count == 0) { return 0; } \
1044 | prng->mod = 1; \
1045 | prng->count_ceil = prng->count; \
1046 | while (prng->mod < prng->count) { prng->mod <<= 1; } \
1047 | prng->state = seed & 0x1fffffff; /* only use lower 29 bits... */ \
1048 | prng->a = (4LU * prng->state) + 1; /* to avoid overflow */ \
1049 | prng->c = primes[(seed * 16451) % sizeof(primes)/sizeof(primes[0])];\
1050 | prng->initialized = 1; \
1051 | return 1; \
1052 | } \
1053 | \
1054 | /* Step the pseudorandom number generator until its state reaches \
1055 | * another test ID between 0 and the test count. \
1056 | * This use a linear congruential pseudorandom number generator, \
1057 | * with the power-of-two ceiling of the test count as the modulus, the \
1058 | * masked seed as the multiplier, and a prime as the increment. For \
1059 | * each generated value < the test count, run the corresponding test. \
1060 | * This will visit all IDs 0 <= X < mod once before repeating, \
1061 | * with a starting position chosen based on the initial seed. \
1062 | * For details, see: Knuth, The Art of Computer Programming \
1063 | * Volume. 2, section 3.2.1. */ \
1064 | void greatest_prng_step(int id) { \
1065 | struct greatest_prng *p = &greatest_info.prng[id]; \
1066 | do { \
1067 | p->state = ((p->a * p->state) + p->c) & (p->mod - 1); \
1068 | } while (p->state >= p->count_ceil); \
1069 | } \
1070 | \
1071 | greatest_type_info greatest_type_info_memory = { \
1072 | greatest_memory_equal_cb, \
1073 | greatest_memory_printf_cb, \
1074 | }; \
1075 | \
1076 | greatest_run_info greatest_info
1077 |
1078 | /* Init internals. */
1079 | #define GREATEST_INIT() \
1080 | do { \
1081 | /* Suppress unused function warning if features aren't used */ \
1082 | (void)greatest_run_suite; \
1083 | (void)greatest_parse_options; \
1084 | (void)greatest_prng_step; \
1085 | (void)greatest_prng_init_first_pass; \
1086 | (void)greatest_prng_init_second_pass; \
1087 | \
1088 | memset(&greatest_info, 0, sizeof(greatest_info)); \
1089 | greatest_info.width = GREATEST_DEFAULT_WIDTH; \
1090 | GREATEST_SET_TIME(greatest_info.begin); \
1091 | } while (0) \
1092 |
1093 | /* Handle command-line arguments, etc. */
1094 | #define GREATEST_MAIN_BEGIN() \
1095 | do { \
1096 | GREATEST_INIT(); \
1097 | greatest_parse_options(argc, argv); \
1098 | } while (0)
1099 |
1100 | /* Report passes, failures, skipped tests, the number of
1101 | * assertions, and the overall run time. */
1102 | #define GREATEST_PRINT_REPORT() \
1103 | do { \
1104 | if (!GREATEST_LIST_ONLY()) { \
1105 | update_counts_and_reset_suite(); \
1106 | GREATEST_SET_TIME(greatest_info.end); \
1107 | GREATEST_FPRINTF(GREATEST_STDOUT, \
1108 | "\nTotal: %u test%s", \
1109 | greatest_info.tests_run, \
1110 | greatest_info.tests_run == 1 ? "" : "s"); \
1111 | GREATEST_CLOCK_DIFF(greatest_info.begin, \
1112 | greatest_info.end); \
1113 | GREATEST_FPRINTF(GREATEST_STDOUT, ", %u assertion%s\n", \
1114 | greatest_info.assertions, \
1115 | greatest_info.assertions == 1 ? "" : "s"); \
1116 | GREATEST_FPRINTF(GREATEST_STDOUT, \
1117 | "Pass: %u, fail: %u, skip: %u.\n", \
1118 | greatest_info.passed, \
1119 | greatest_info.failed, greatest_info.skipped); \
1120 | } \
1121 | } while (0)
1122 |
1123 | /* Report results, exit with exit status based on results. */
1124 | #define GREATEST_MAIN_END() \
1125 | do { \
1126 | GREATEST_PRINT_REPORT(); \
1127 | return (greatest_all_passed() ? EXIT_SUCCESS : EXIT_FAILURE); \
1128 | } while (0)
1129 |
1130 | /* Make abbreviations without the GREATEST_ prefix for the
1131 | * most commonly used symbols. */
1132 | #if GREATEST_USE_ABBREVS
1133 | #define TEST GREATEST_TEST
1134 | #define SUITE GREATEST_SUITE
1135 | #define SUITE_EXTERN GREATEST_SUITE_EXTERN
1136 | #define RUN_TEST GREATEST_RUN_TEST
1137 | #define RUN_TEST1 GREATEST_RUN_TEST1
1138 | #define RUN_SUITE GREATEST_RUN_SUITE
1139 | #define IGNORE_TEST GREATEST_IGNORE_TEST
1140 | #define ASSERT GREATEST_ASSERT
1141 | #define ASSERTm GREATEST_ASSERTm
1142 | #define ASSERT_FALSE GREATEST_ASSERT_FALSE
1143 | #define ASSERT_EQ GREATEST_ASSERT_EQ
1144 | #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT
1145 | #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE
1146 | #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T
1147 | #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ
1148 | #define ASSERT_STRN_EQ GREATEST_ASSERT_STRN_EQ
1149 | #define ASSERT_MEM_EQ GREATEST_ASSERT_MEM_EQ
1150 | #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ
1151 | #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm
1152 | #define ASSERT_EQm GREATEST_ASSERT_EQm
1153 | #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm
1154 | #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm
1155 | #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm
1156 | #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm
1157 | #define ASSERT_STRN_EQm GREATEST_ASSERT_STRN_EQm
1158 | #define ASSERT_MEM_EQm GREATEST_ASSERT_MEM_EQm
1159 | #define ASSERT_ENUM_EQm GREATEST_ASSERT_ENUM_EQm
1160 | #define PASS GREATEST_PASS
1161 | #define FAIL GREATEST_FAIL
1162 | #define SKIP GREATEST_SKIP
1163 | #define PASSm GREATEST_PASSm
1164 | #define FAILm GREATEST_FAILm
1165 | #define SKIPm GREATEST_SKIPm
1166 | #define SET_SETUP GREATEST_SET_SETUP_CB
1167 | #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB
1168 | #define CHECK_CALL GREATEST_CHECK_CALL
1169 | #define SHUFFLE_TESTS GREATEST_SHUFFLE_TESTS
1170 | #define SHUFFLE_SUITES GREATEST_SHUFFLE_SUITES
1171 |
1172 | #ifdef GREATEST_VA_ARGS
1173 | #define RUN_TESTp GREATEST_RUN_TESTp
1174 | #endif
1175 |
1176 | #if GREATEST_USE_LONGJMP
1177 | #define ASSERT_OR_LONGJMP GREATEST_ASSERT_OR_LONGJMP
1178 | #define ASSERT_OR_LONGJMPm GREATEST_ASSERT_OR_LONGJMPm
1179 | #define FAIL_WITH_LONGJMP GREATEST_FAIL_WITH_LONGJMP
1180 | #define FAIL_WITH_LONGJMPm GREATEST_FAIL_WITH_LONGJMPm
1181 | #endif
1182 |
1183 | #endif /* USE_ABBREVS */
1184 |
1185 | #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS)
1186 | }
1187 | #endif
1188 |
1189 | #endif
1190 |
--------------------------------------------------------------------------------
/deps/greatest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "greatest",
3 | "version": "v1.2.1",
4 | "repo": "silentbicycle/greatest",
5 | "src": ["greatest.h"],
6 | "description": "A C testing library in 1 file. No dependencies, no dynamic allocation.",
7 | "license": "ISC",
8 | "keywords": ["test", "unit", "testing"]
9 | }
10 |
--------------------------------------------------------------------------------
/deps/theft/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | test_theft
3 | libtheft.a
4 |
--------------------------------------------------------------------------------
/deps/theft/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Scott Vokes
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any
4 | purpose with or without fee is hereby granted, provided that the above
5 | copyright notice and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 |
--------------------------------------------------------------------------------
/deps/theft/theft.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "theft.h"
4 | #include "theft_types_internal.h"
5 | #include "theft_bloom.h"
6 | #include "theft_mt.h"
7 |
8 | /* Initialize a theft test runner.
9 | * BLOOM_BITS sets the size of the table used for detecting
10 | * combinations of arguments that have already been tested.
11 | * If 0, a default size will be chosen based on trial count.
12 | * (This will only be used if all property types have hash
13 | * callbacks defined.) The bloom filter can also be disabled
14 | * by setting BLOOM_BITS to THEFT_BLOOM_DISABLE.
15 | *
16 | * Returns a NULL if malloc fails or BLOOM_BITS is out of bounds. */
17 | struct theft *theft_init(uint8_t bloom_bits) {
18 | if ((bloom_bits != 0 && (bloom_bits < THEFT_BLOOM_BITS_MIN))
19 | || ((bloom_bits > THEFT_BLOOM_BITS_MAX) &&
20 | bloom_bits != THEFT_BLOOM_DISABLE)) {
21 | return NULL;
22 | }
23 |
24 | theft *t = malloc(sizeof(*t));
25 | if (t == NULL) { return NULL; }
26 | memset(t, 0, sizeof(*t));
27 |
28 | t->mt = theft_mt_init(DEFAULT_THEFT_SEED);
29 | if (t->mt == NULL) {
30 | free(t);
31 | return NULL;
32 | } else {
33 | t->out = stdout;
34 | t->requested_bloom_bits = bloom_bits;
35 | return t;
36 | }
37 | }
38 |
39 | /* (Re-)initialize the random number generator with a specific seed. */
40 | void theft_set_seed(struct theft *t, uint64_t seed) {
41 | t->seed = seed;
42 | theft_mt_reset(t->mt, seed);
43 | }
44 |
45 | /* Get a random 64-bit integer from the test runner's PRNG. */
46 | theft_seed theft_random(struct theft *t) {
47 | theft_seed ns = (theft_seed)theft_mt_random(t->mt);
48 | return ns;
49 | }
50 |
51 | /* Get a random double from the test runner's PRNG. */
52 | double theft_random_double(struct theft *t) {
53 | return theft_mt_random_double(t->mt);
54 | }
55 |
56 | /* Change T's output stream handle to OUT. (Default: stdout.) */
57 | void theft_set_output_stream(struct theft *t, FILE *out) { t->out = out; }
58 |
59 | /* Check if all argument info structs have all required callbacks. */
60 | static bool
61 | check_all_args(struct theft_propfun_info *info, bool *all_hashable) {
62 | bool ah = true;
63 | for (int i = 0; i < info->arity; i++) {
64 | struct theft_type_info *ti = info->type_info[i];
65 | if (ti->alloc == NULL) { return false; }
66 | if (ti->hash == NULL) { ah = false; }
67 | }
68 | *all_hashable = ah;
69 | return true;
70 | }
71 |
72 | static theft_progress_callback_res
73 | default_progress_cb(struct theft_trial_info *info, void *env) {
74 | (void)info;
75 | (void)env;
76 | return THEFT_PROGRESS_CONTINUE;
77 | }
78 |
79 | static void infer_arity(struct theft_propfun_info *info) {
80 | for (int i = 0; i < THEFT_MAX_ARITY; i++) {
81 | if (info->type_info[i] == NULL) {
82 | info->arity = i;
83 | break;
84 | }
85 | }
86 | }
87 |
88 | /* Run a series of randomized trials of a property function.
89 | *
90 | * Configuration is specified in CFG; many fields are optional.
91 | * See the type definition in `theft_types.h`. */
92 | theft_run_res
93 | theft_run(struct theft *t, struct theft_cfg *cfg) {
94 | if (t == NULL || cfg == NULL) {
95 | return THEFT_RUN_ERROR_BAD_ARGS;
96 | }
97 |
98 | struct theft_propfun_info info;
99 | memset(&info, 0, sizeof(info));
100 | info.name = cfg->name;
101 | info.fun = cfg->fun;
102 | memcpy(info.type_info, cfg->type_info, sizeof(info.type_info));
103 | info.always_seed_count = cfg->always_seed_count;
104 | info.always_seeds = cfg->always_seeds;
105 |
106 | if (cfg->seed) {
107 | theft_set_seed(t, cfg->seed);
108 | } else {
109 | theft_set_seed(t, DEFAULT_THEFT_SEED);
110 | }
111 |
112 | if (cfg->trials == 0) { cfg->trials = THEFT_DEF_TRIALS; }
113 |
114 | return theft_run_internal(t, &info, cfg->trials, cfg->progress_cb,
115 | cfg->env, cfg->report);
116 | }
117 |
118 | /* Actually run the trials, with all arguments made explicit. */
119 | static theft_run_res
120 | theft_run_internal(struct theft *t, struct theft_propfun_info *info,
121 | int trials, theft_progress_cb *cb, void *env,
122 | struct theft_trial_report *r) {
123 |
124 | struct theft_trial_report fake_report;
125 | if (r == NULL) { r = &fake_report; }
126 | memset(r, 0, sizeof(*r));
127 |
128 | infer_arity(info);
129 | if (info->arity == 0) {
130 | return THEFT_RUN_ERROR_BAD_ARGS;
131 | }
132 |
133 | if (t == NULL || info == NULL || info->fun == NULL
134 | || info->arity == 0) {
135 | return THEFT_RUN_ERROR_BAD_ARGS;
136 | }
137 |
138 | bool all_hashable = false;
139 | if (!check_all_args(info, &all_hashable)) {
140 | return THEFT_RUN_ERROR_MISSING_CALLBACK;
141 | }
142 |
143 | if (cb == NULL) { cb = default_progress_cb; }
144 |
145 | /* If all arguments are hashable, then attempt to use
146 | * a bloom filter to avoid redundant checking. */
147 | if (all_hashable) {
148 | if (t->requested_bloom_bits == 0) {
149 | t->requested_bloom_bits = theft_bloom_recommendation(trials);
150 | }
151 | if (t->requested_bloom_bits != THEFT_BLOOM_DISABLE) {
152 | t->bloom = theft_bloom_init(t->requested_bloom_bits);
153 | }
154 | }
155 |
156 | theft_seed seed = t->seed;
157 | theft_seed initial_seed = t->seed;
158 | int always_seeds = info->always_seed_count;
159 | if (info->always_seeds == NULL) { always_seeds = 0; }
160 |
161 | void *args[THEFT_MAX_ARITY];
162 |
163 | theft_progress_callback_res cres = THEFT_PROGRESS_CONTINUE;
164 |
165 | for (int trial = 0; trial < trials; trial++) {
166 | memset(args, 0xFF, sizeof(args));
167 | if (cres == THEFT_PROGRESS_HALT) { break; }
168 |
169 | /* If any seeds to always run were specified, use those before
170 | * reverting to the specified starting seed. */
171 | if (trial < always_seeds) {
172 | seed = info->always_seeds[trial];
173 | } else if ((always_seeds > 0) && (trial == always_seeds)) {
174 | seed = initial_seed;
175 | }
176 |
177 | struct theft_trial_info ti = {
178 | .name = info->name,
179 | .trial = trial,
180 | .seed = seed,
181 | .arity = info->arity,
182 | .args = args
183 | };
184 |
185 | theft_set_seed(t, seed);
186 | all_gen_res_t gres = gen_all_args(t, info, seed, args, env);
187 | switch (gres) {
188 | case ALL_GEN_SKIP:
189 | /* skip generating these args */
190 | ti.status = THEFT_TRIAL_SKIP;
191 | r->skip++;
192 | cres = cb(&ti, env);
193 | break;
194 | case ALL_GEN_DUP:
195 | /* skip these args -- probably already tried */
196 | ti.status = THEFT_TRIAL_DUP;
197 | r->dup++;
198 | cres = cb(&ti, env);
199 | break;
200 | default:
201 | case ALL_GEN_ERROR:
202 | /* Error while generating args */
203 | ti.status = THEFT_TRIAL_ERROR;
204 | cres = cb(&ti, env);
205 | return THEFT_RUN_ERROR;
206 | case ALL_GEN_OK:
207 | /* (Extracted function to avoid deep nesting here.) */
208 | if (!run_trial(t, info, args, cb, env, r, &ti, &cres)) {
209 | return THEFT_RUN_ERROR;
210 | }
211 | }
212 |
213 | free_args(info, args, env);
214 |
215 | /* Restore last known seed and generate next. */
216 | theft_set_seed(t, seed);
217 | seed = theft_random(t);
218 | }
219 |
220 | if (r->fail > 0) {
221 | return THEFT_RUN_FAIL;
222 | } else {
223 | return THEFT_RUN_PASS;
224 | }
225 | }
226 |
227 | /* Now that arguments have been generated, run the trial and update
228 | * counters, call cb with results, etc. */
229 | static bool run_trial(struct theft *t, struct theft_propfun_info *info,
230 | void **args, theft_progress_cb *cb, void *env,
231 | struct theft_trial_report *r, struct theft_trial_info *ti,
232 | theft_progress_callback_res *cres) {
233 | if (t->bloom) { mark_called(t, info, args, env); }
234 | theft_trial_res tres = call_fun(info, args);
235 | ti->status = tres;
236 | switch (tres) {
237 | case THEFT_TRIAL_PASS:
238 | r->pass++;
239 | *cres = cb(ti, env);
240 | break;
241 | case THEFT_TRIAL_FAIL:
242 | if (!attempt_to_shrink(t, info, args, env)) {
243 | ti->status = THEFT_TRIAL_ERROR;
244 | *cres = cb(ti, env);
245 | return false;
246 | }
247 | r->fail++;
248 | *cres = report_on_failure(t, info, ti, cb, env);
249 | break;
250 | case THEFT_TRIAL_SKIP:
251 | *cres = cb(ti, env);
252 | r->skip++;
253 | break;
254 | case THEFT_TRIAL_DUP:
255 | /* user callback should not return this; fall through */
256 | case THEFT_TRIAL_ERROR:
257 | *cres = cb(ti, env);
258 | free_args(info, args, env);
259 | return false;
260 | }
261 | return true;
262 | }
263 |
264 | static void free_args(struct theft_propfun_info *info,
265 | void **args, void *env) {
266 | for (int i = 0; i < info->arity; i++) {
267 | theft_free_cb *fcb = info->type_info[i]->free;
268 | if (fcb && args[i] != THEFT_SKIP) {
269 | fcb(args[i], env);
270 | }
271 | }
272 | }
273 |
274 | void theft_free(struct theft *t) {
275 | if (t->bloom) {
276 | theft_bloom_dump(t->bloom);
277 | theft_bloom_free(t->bloom);
278 | t->bloom = NULL;
279 | }
280 | theft_mt_free(t->mt);
281 | free(t);
282 | }
283 |
284 | /* Actually call the property function. Its number of arguments is not
285 | * constrained by the typedef, but will be defined at the call site
286 | * here. (If info->arity is wrong, it will probably crash.) */
287 | static theft_trial_res
288 | call_fun(struct theft_propfun_info *info, void **args) {
289 | theft_trial_res res = THEFT_TRIAL_ERROR;
290 | switch (info->arity) {
291 | case 1:
292 | res = info->fun(args[0]);
293 | break;
294 | case 2:
295 | res = info->fun(args[0], args[1]);
296 | break;
297 | case 3:
298 | res = info->fun(args[0], args[1], args[2]);
299 | break;
300 | case 4:
301 | res = info->fun(args[0], args[1], args[2], args[3]);
302 | break;
303 | case 5:
304 | res = info->fun(args[0], args[1], args[2], args[3], args[4]);
305 | break;
306 | case 6:
307 | res = info->fun(args[0], args[1], args[2], args[3], args[4],
308 | args[5]);
309 | break;
310 | case 7:
311 | res = info->fun(args[0], args[1], args[2], args[3], args[4],
312 | args[5], args[6]);
313 | break;
314 | case 8:
315 | res = info->fun(args[0], args[1], args[2], args[3], args[4],
316 | args[5], args[6], args[7]);
317 | break;
318 | case 9:
319 | res = info->fun(args[0], args[1], args[2], args[3], args[4],
320 | args[5], args[6], args[7], args[8]);
321 | break;
322 | case 10:
323 | res = info->fun(args[0], args[1], args[2], args[3], args[4],
324 | args[5], args[6], args[7], args[8], args[9]);
325 | break;
326 | /* ... */
327 | default:
328 | return THEFT_TRIAL_ERROR;
329 | }
330 | return res;
331 | }
332 |
333 | /* Attempt to instantiate arguments, starting with the current seed. */
334 | static all_gen_res_t
335 | gen_all_args(theft *t, struct theft_propfun_info *info,
336 | theft_seed seed, void *args[THEFT_MAX_ARITY], void *env) {
337 | for (int i = 0; i < info->arity; i++) {
338 | struct theft_type_info *ti = info->type_info[i];
339 | void *p = ti->alloc(t, seed, env);
340 | if (p == THEFT_SKIP || p == THEFT_ERROR) {
341 | for (int j = 0; j < i; j++) {
342 | ti->free(args[j], env);
343 | }
344 | if (p == THEFT_SKIP) {
345 | return ALL_GEN_SKIP;
346 | } else {
347 | return ALL_GEN_ERROR;
348 | }
349 | } else {
350 | args[i] = p;
351 | }
352 | seed = theft_random(t);
353 | }
354 |
355 | /* check bloom filter */
356 | if (t->bloom && check_called(t, info, args, env)) {
357 | return ALL_GEN_DUP;
358 | }
359 |
360 | return ALL_GEN_OK;
361 | }
362 |
363 | /* Attempt to simplify all arguments, breadth first. Continue as long as
364 | * progress is made, i.e., until a local minima is reached. */
365 | static bool
366 | attempt_to_shrink(theft *t, struct theft_propfun_info *info,
367 | void *args[], void *env) {
368 | bool progress = false;
369 | do {
370 | progress = false;
371 | for (int ai = 0; ai < info->arity; ai++) {
372 | struct theft_type_info *ti = info->type_info[ai];
373 | if (ti->shrink) {
374 | /* attempt to simplify this argument by one step */
375 | shrink_res rres = attempt_to_shrink_arg(t, info, args, env, ai);
376 | switch (rres) {
377 | case SHRINK_OK:
378 | progress = true;
379 | break;
380 | case SHRINK_DEAD_END:
381 | break;
382 | default:
383 | case SHRINK_ERROR:
384 | return false;
385 | }
386 | }
387 | }
388 | } while (progress);
389 |
390 | return true;
391 | }
392 |
393 | /* Simplify an argument by trying all of its simplification tactics, in
394 | * order, and checking whether the property still fails. If it passes,
395 | * then revert the simplification and try another tactic.
396 | *
397 | * If the bloom filter is being used (i.e., if all arguments have hash
398 | * callbacks defined), then use it to skip over areas of the state
399 | * space that have probably already been tried. */
400 | static shrink_res
401 | attempt_to_shrink_arg(theft *t, struct theft_propfun_info *info,
402 | void *args[], void *env, int ai) {
403 | struct theft_type_info *ti = info->type_info[ai];
404 |
405 | for (uint32_t tactic = 0; tactic < THEFT_MAX_TACTICS; tactic++) {
406 | void *cur = args[ai];
407 | void *nv = ti->shrink(cur, tactic, env);
408 | if (nv == THEFT_NO_MORE_TACTICS) {
409 | return SHRINK_DEAD_END;
410 | } else if (nv == THEFT_ERROR) {
411 | return SHRINK_ERROR;
412 | } else if (nv == THEFT_DEAD_END) {
413 | continue; /* try next tactic */
414 | }
415 |
416 | args[ai] = nv;
417 | if (t->bloom) {
418 | if (check_called(t, info, args, env)) {
419 | /* probably redundant */
420 | if (ti->free) { ti->free(nv, env); }
421 | args[ai] = cur;
422 | continue;
423 | } else {
424 | mark_called(t, info, args, env);
425 | }
426 | }
427 | theft_trial_res res = call_fun(info, args);
428 |
429 | switch (res) {
430 | case THEFT_TRIAL_PASS:
431 | case THEFT_TRIAL_SKIP:
432 | /* revert */
433 | args[ai] = cur;
434 | if (ti->free) { ti->free(nv, env); }
435 | break;
436 | case THEFT_TRIAL_FAIL:
437 | if (ti->free) { ti->free(cur, env); }
438 | return SHRINK_OK;
439 | case THEFT_TRIAL_DUP: /* user callback should not return this */
440 | case THEFT_TRIAL_ERROR:
441 | return SHRINK_ERROR;
442 | }
443 | }
444 | (void)t;
445 | return SHRINK_DEAD_END;
446 | }
447 |
448 | /* Populate a buffer with hashes of all the arguments. */
449 | static void get_arg_hash_buffer(theft_hash *buffer,
450 | struct theft_propfun_info *info, void **args, void *env) {
451 | for (int i = 0; i < info->arity; i++) {
452 | buffer[i] = info->type_info[i]->hash(args[i], env);
453 | }
454 | }
455 |
456 | /* Mark the tuple of argument instances as called in the bloom filter. */
457 | static void mark_called(theft *t, struct theft_propfun_info *info,
458 | void **args, void *env) {
459 | theft_hash buffer[THEFT_MAX_ARITY];
460 | get_arg_hash_buffer(buffer, info, args, env);
461 | theft_bloom_mark(t->bloom, (uint8_t *)buffer,
462 | info->arity * sizeof(theft_hash));
463 | }
464 |
465 | /* Check if this combination of argument instances has been called. */
466 | static bool check_called(theft *t, struct theft_propfun_info *info,
467 | void **args, void *env) {
468 | theft_hash buffer[THEFT_MAX_ARITY];
469 | get_arg_hash_buffer(buffer, info, args, env);
470 | return theft_bloom_check(t->bloom, (uint8_t *)buffer,
471 | info->arity * sizeof(theft_hash));
472 | }
473 |
474 | /* Print info about a failure. */
475 | static theft_progress_callback_res report_on_failure(theft *t,
476 | struct theft_propfun_info *info,
477 | struct theft_trial_info *ti, theft_progress_cb *cb, void *env) {
478 | static theft_progress_callback_res cres;
479 |
480 | int arity = info->arity;
481 | fprintf(t->out, "\n\n -- Counter-Example: %s\n",
482 | info->name ? info-> name : "");
483 | fprintf(t->out, " Trial %u, Seed 0x%016llx\n", ti->trial,
484 | (uint64_t)ti->seed);
485 | for (int i = 0; i < arity; i++) {
486 | theft_print_cb *print = info->type_info[i]->print;
487 | if (print) {
488 | fprintf(t->out, " Argument %d:\n", i);
489 | print(t->out, ti->args[i], env);
490 | fprintf(t->out, "\n");
491 | }
492 | }
493 |
494 | cres = cb(ti, env);
495 | return cres;
496 | }
497 |
--------------------------------------------------------------------------------
/deps/theft/theft.h:
--------------------------------------------------------------------------------
1 | #ifndef THEFT_H
2 | #define THEFT_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | /* Version 0.2.0 */
10 | #define THEFT_VERSION_MAJOR 0
11 | #define THEFT_VERSION_MINOR 2
12 | #define THEFT_VERSION_PATCH 0
13 |
14 | /* A property can have at most this many arguments. */
15 | #define THEFT_MAX_ARITY 10
16 |
17 | #include "theft_types.h"
18 |
19 | /* Default number of trials to run. */
20 | #define THEFT_DEF_TRIALS 100
21 |
22 | /* Min and max bits used to determine bloom filter size.
23 | * (A larger value uses more memory, but reduces the odds of an
24 | * untested argument combination being falsely skipped.) */
25 | #define THEFT_BLOOM_BITS_MIN 13 /* 1 KB */
26 | #define THEFT_BLOOM_BITS_MAX 33 /* 1 GB */
27 |
28 | /* Initialize a theft test runner.
29 | * BLOOM_BITS sets the size of the table used for detecting
30 | * combinations of arguments that have already been tested.
31 | * If 0, a default size will be chosen based on trial count.
32 | * (This will only be used if all property types have hash
33 | * callbacks defined.) The bloom filter can also be disabled
34 | * by setting BLOOM_BITS to THEFT_BLOOM_DISABLE.
35 | *
36 | * Returns a NULL if malloc fails or BLOOM_BITS is out of bounds. */
37 | struct theft *theft_init(uint8_t bloom_bits);
38 |
39 | /* Free a property-test runner. */
40 | void theft_free(struct theft *t);
41 |
42 | /* (Re-)initialize the random number generator with a specific seed. */
43 | void theft_set_seed(struct theft *t, uint64_t seed);
44 |
45 | /* Get a random 64-bit integer from the test runner's PRNG. */
46 | theft_hash theft_random(struct theft *t);
47 |
48 | /* Get a random double from the test runner's PRNG. */
49 | double theft_random_double(struct theft *t);
50 |
51 | /* Change T's output stream handle to OUT. (Default: stdout.) */
52 | void theft_set_output_stream(struct theft *t, FILE *out);
53 |
54 | /* Run a series of randomized trials of a property function.
55 | *
56 | * Configuration is specified in CFG; many fields are optional.
57 | * See the type definition in `theft_types.h`. */
58 | theft_run_res
59 | theft_run(struct theft *t, struct theft_cfg *cfg);
60 |
61 | /* Hash a buffer in one pass. (Wraps the below functions.) */
62 | theft_hash theft_hash_onepass(uint8_t *data, size_t bytes);
63 |
64 | /* Init/reset a hasher for incremental hashing.
65 | * Returns true, or false if you gave it a NULL pointer. */
66 | void theft_hash_init(struct theft_hasher *h);
67 |
68 | /* Sink more data into an incremental hash. */
69 | void theft_hash_sink(struct theft_hasher *h, uint8_t *data, size_t bytes);
70 |
71 | /* Finish hashing and get the result. */
72 | theft_hash theft_hash_done(struct theft_hasher *h);
73 |
74 | #endif
75 |
--------------------------------------------------------------------------------
/deps/theft/theft_bloom.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "theft.h"
5 | #include "theft_bloom.h"
6 |
7 | #define DEFAULT_BLOOM_BITS 17
8 | #define DEBUG_BLOOM_FILTER 0
9 | #define LOG2_BITS_PER_BYTE 3
10 | #define HASH_COUNT 4
11 |
12 | static uint8_t get_bits_set_count(uint8_t counts[256], uint8_t byte);
13 |
14 | /* Initialize a bloom filter. */
15 | struct theft_bloom *theft_bloom_init(uint8_t bit_size2) {
16 | size_t sz = 1 << (bit_size2 - LOG2_BITS_PER_BYTE);
17 | struct theft_bloom *b = malloc(sizeof(struct theft_bloom) + sz);
18 | if (b) {
19 | b->size = sz;
20 | b->bit_count = bit_size2;
21 | memset(b->bits, 0, sz);
22 | }
23 | return b;
24 | }
25 |
26 | /* Hash data and mark it in the bloom filter. */
27 | void theft_bloom_mark(struct theft_bloom *b, uint8_t *data, size_t data_size) {
28 | uint64_t hash = theft_hash_onepass(data, data_size);
29 | uint8_t bc = b->bit_count;
30 | uint64_t mask = (1 << bc) - 1;
31 |
32 | /* Use HASH_COUNT distinct slices of MASK bits as hashes for the bloom filter. */
33 | int bit_inc = (64 - bc) / HASH_COUNT;
34 |
35 | for (int i = 0; i < (64 - bc); i += bit_inc) {
36 | uint64_t v = (hash & (mask << i)) >> i;
37 | size_t offset = v / 8;
38 | uint8_t bit = 1 << (v & 0x07);
39 | b->bits[offset] |= bit;
40 | }
41 | }
42 |
43 | /* Check whether the data's hash is in the bloom filter. */
44 | bool theft_bloom_check(struct theft_bloom *b, uint8_t *data, size_t data_size) {
45 | uint64_t hash = theft_hash_onepass(data, data_size);
46 | uint8_t bc = b->bit_count;
47 | uint64_t mask = (1 << bc) - 1;
48 |
49 | int bit_inc = (64 - bc) / HASH_COUNT;
50 |
51 | for (int i = 0; i < (64 - bc); i += bit_inc) {
52 | uint64_t v = (hash & (mask << i)) >> i;
53 | size_t offset = v / 8;
54 | uint8_t bit = 1 << (v & 0x07);
55 | if (0 == (b->bits[offset] & bit)) { return false; }
56 | }
57 |
58 | return true;
59 | }
60 |
61 | /* Free the bloom filter. */
62 | void theft_bloom_free(struct theft_bloom *b) { free(b); }
63 |
64 | /* Dump the bloom filter's contents. (Debugging.) */
65 | void theft_bloom_dump(struct theft_bloom *b) {
66 | uint8_t counts[256];
67 | memset(counts, 0xFF, sizeof(counts));
68 |
69 | size_t total = 0;
70 | uint16_t row_total = 0;
71 |
72 | for (size_t i = 0; i < b->size; i++) {
73 | uint8_t count = get_bits_set_count(counts, b->bits[i]);
74 | total += count;
75 | row_total += count;
76 | #if DEBUG_BLOOM_FILTER > 1
77 | char c = (count == 0 ? '.' : '0' + count);
78 | printf("%c", c);
79 | if ((i & 63) == 0 || i == b->size - 1) {
80 | printf(" - %2.1f%%\n",
81 | (100 * row_total) / (64.0 * 8));
82 | row_total = 0;
83 | }
84 | #endif
85 | }
86 |
87 | #if DEBUG_BLOOM_FILTER
88 | printf(" -- bloom filter: %zd of %zd bits set (%2d%%)\n",
89 | total, 8*b->size, (int)((100.0 * total)/(8.0*b->size)));
90 | #endif
91 |
92 | /* If the total number of bits set is > the number of bytes
93 | * in the table (i.e., > 1/8 full) then warn the user. */
94 | if (total > b->size) {
95 | fprintf(stderr, "\nWARNING: bloom filter is %zd%% full, "
96 | "larger bloom_bits value recommended.\n",
97 | (size_t)((100 * total) / (8 * b->size)));
98 | }
99 | }
100 |
101 | /* Recommend a bloom filter size for a given number of trials. */
102 | uint8_t theft_bloom_recommendation(int trials) {
103 | /* With a preferred priority of false positives under 0.1%,
104 | * the required number of bits m in the bloom filter is:
105 | * m = -lg(0.001)/(lg(2)^2) == 14.378 ~= 14,
106 | * so we want an array with 1 << ceil(log2(14*trials)) cells.
107 | *
108 | * Note: The above formula is for the *ideal* number of hash
109 | * functions, but we're using a hardcoded count. It appears to work
110 | * well enough in practice, though, and this can be adjusted later
111 | * without impacting the API. */
112 | #define TRIAL_MULTIPILER 14
113 | uint8_t res = DEFAULT_BLOOM_BITS;
114 |
115 | const uint8_t min = THEFT_BLOOM_BITS_MIN - LOG2_BITS_PER_BYTE;
116 | const uint8_t max = THEFT_BLOOM_BITS_MAX - LOG2_BITS_PER_BYTE;
117 |
118 | for (uint8_t i = min; i < max; i++) {
119 | int32_t v = (1 << i);
120 | if (v > (TRIAL_MULTIPILER * trials)) {
121 | res = i + LOG2_BITS_PER_BYTE;
122 | break;
123 | }
124 | }
125 |
126 | #if DEBUG_BLOOM_FILTER
127 | size_t sz = 1 << (res - LOG2_BITS_PER_BYTE);
128 | printf("Using %zd bytes for bloom filter: %d trials -> bit_size2 %u\n",
129 | sizeof(struct theft_bloom) + sz, trials, res);
130 | #endif
131 |
132 | return res;
133 | }
134 |
135 | /* Check a byte->bits set table, and lazily populate it. */
136 | static uint8_t get_bits_set_count(uint8_t counts[256], uint8_t byte) {
137 | uint8_t v = counts[byte];
138 | if (v != 0xFF) { return v; }
139 | uint8_t t = 0;
140 | for (uint8_t i = 0; i < 8; i++) {
141 | if (byte & (1 << i)) { t++; }
142 | }
143 | counts[byte] = t;
144 | return t;
145 | }
146 |
--------------------------------------------------------------------------------
/deps/theft/theft_bloom.h:
--------------------------------------------------------------------------------
1 | #ifndef THEFT_BLOOM_H
2 | #define THEFT_BLOOM_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | struct theft_bloom {
9 | uint8_t bit_count;
10 | size_t size;
11 | uint8_t bits[];
12 | };
13 |
14 | /* Initialize a bloom filter. */
15 | struct theft_bloom *theft_bloom_init(uint8_t bit_size2);
16 |
17 | /* Hash data and mark it in the bloom filter. */
18 | void theft_bloom_mark(struct theft_bloom *b, uint8_t *data, size_t data_size);
19 |
20 | /* Check whether the data's hash is in the bloom filter. */
21 | bool theft_bloom_check(struct theft_bloom *b, uint8_t *data, size_t data_size);
22 |
23 | /* Free the bloom filter. */
24 | void theft_bloom_free(struct theft_bloom *b);
25 |
26 | /* Dump the bloom filter's contents. (Debugging.) */
27 | void theft_bloom_dump(struct theft_bloom *b);
28 |
29 | /* Recommend a bloom filter size for a given number of trials. */
30 | uint8_t theft_bloom_recommendation(int trials);
31 |
32 |
33 | #endif
34 |
--------------------------------------------------------------------------------
/deps/theft/theft_hash.c:
--------------------------------------------------------------------------------
1 | #include "theft.h"
2 |
3 | /* Fowler/Noll/Vo hash, 64-bit FNV-1a.
4 | * This hashing algorithm is in the public domain.
5 | * For more details, see: http://www.isthe.com/chongo/tech/comp/fnv/. */
6 | static const uint64_t fnv64_prime = 1099511628211L;
7 | static const uint64_t fnv64_offset_basis = 14695981039346656037UL;
8 |
9 | /* Init a hasher for incremental hashing. */
10 | void theft_hash_init(struct theft_hasher *h) {
11 | h->accum = fnv64_offset_basis;
12 | }
13 |
14 | /* Sink more data into an incremental hash. */
15 | void theft_hash_sink(struct theft_hasher *h, uint8_t *data, size_t bytes) {
16 | if (h == NULL || data == NULL) { return; }
17 | uint64_t a = h->accum;
18 | for (size_t i = 0; i < bytes; i++) {
19 | a = (a ^ data[i]) * fnv64_prime;
20 | }
21 | h->accum = a;
22 | }
23 |
24 | /* Finish hashing and get the result. */
25 | theft_hash theft_hash_done(struct theft_hasher *h) {
26 | theft_hash res = h->accum;
27 | theft_hash_init(h); /* reset */
28 | return res;
29 | }
30 |
31 | /* Hash a buffer in one pass. (Wraps the above functions.) */
32 | theft_hash theft_hash_onepass(uint8_t *data, size_t bytes) {
33 | struct theft_hasher h;
34 | theft_hash_init(&h);
35 | theft_hash_sink(&h, data, bytes);
36 | return theft_hash_done(&h);
37 | }
38 |
--------------------------------------------------------------------------------
/deps/theft/theft_mt.c:
--------------------------------------------------------------------------------
1 | /*
2 | A C-program for MT19937-64 (2004/9/29 version).
3 | Coded by Takuji Nishimura and Makoto Matsumoto.
4 |
5 | This is a 64-bit version of Mersenne Twister pseudorandom number
6 | generator.
7 |
8 | Before using, initialize the state by using init_genrand64(seed)
9 | or init_by_array64(init_key, key_length).
10 |
11 | Copyright (C) 2004, Makoto Matsumoto and Takuji Nishimura,
12 | All rights reserved.
13 |
14 | Redistribution and use in source and binary forms, with or without
15 | modification, are permitted provided that the following conditions
16 | are met:
17 |
18 | 1. Redistributions of source code must retain the above copyright
19 | notice, this list of conditions and the following disclaimer.
20 |
21 | 2. Redistributions in binary form must reproduce the above copyright
22 | notice, this list of conditions and the following disclaimer in the
23 | documentation and/or other materials provided with the distribution.
24 |
25 | 3. The names of its contributors may not be used to endorse or promote
26 | products derived from this software without specific prior written
27 | permission.
28 |
29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
33 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
34 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
37 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
39 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 |
41 | References:
42 | T. Nishimura, ``Tables of 64-bit Mersenne Twisters''
43 | ACM Transactions on Modeling and
44 | Computer Simulation 10. (2000) 348--357.
45 | M. Matsumoto and T. Nishimura,
46 | ``Mersenne Twister: a 623-dimensionally equidistributed
47 | uniform pseudorandom number generator''
48 | ACM Transactions on Modeling and
49 | Computer Simulation 8. (Jan. 1998) 3--30.
50 |
51 | Any feedback is very welcome.
52 | http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html
53 | email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces)
54 | */
55 |
56 | /* The code has been modified to store internal state in heap/stack
57 | * allocated memory, rather than statically allocated memory, to allow
58 | * multiple instances running in the same address space. */
59 |
60 | #include
61 | #include
62 | #include "theft_mt.h"
63 |
64 | #define NN THEFT_MT_PARAM_N
65 | #define MM 156
66 | #define MATRIX_A 0xB5026F5AA96619E9ULL
67 | #define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */
68 | #define LM 0x7FFFFFFFULL /* Least significant 31 bits */
69 |
70 | static uint64_t genrand64_int64(struct theft_mt *r);
71 |
72 | /* Heap-allocate a mersenne twister struct. */
73 | struct theft_mt *theft_mt_init(uint64_t seed) {
74 | struct theft_mt *mt = malloc(sizeof(struct theft_mt));
75 | if (mt == NULL) { return NULL; }
76 | theft_mt_reset(mt, seed);
77 | return mt;
78 | }
79 |
80 | /* Free a heap-allocated mersenne twister struct. */
81 | void theft_mt_free(struct theft_mt *mt) {
82 | free(mt);
83 | }
84 |
85 | /* initializes mt[NN] with a seed */
86 | void theft_mt_reset(struct theft_mt *mt, uint64_t seed)
87 | {
88 | mt->mt[0] = seed;
89 | uint16_t mti = 0;
90 | for (mti=1; mtimt[mti] = (6364136223846793005ULL *
92 | (mt->mt[mti-1] ^ (mt->mt[mti-1] >> 62)) + mti);
93 | }
94 | mt->mti = mti;
95 | }
96 |
97 | /* Get a 64-bit random number. */
98 | uint64_t theft_mt_random(struct theft_mt *mt) {
99 | return genrand64_int64(mt);
100 | }
101 |
102 | /* Generate a random number on [0,1]-real-interval. */
103 | double theft_mt_random_double(struct theft_mt *mt)
104 | {
105 | return (genrand64_int64(mt) >> 11) * (1.0/9007199254740991.0);
106 | }
107 |
108 | /* generates a random number on [0, 2^64-1]-interval */
109 | static uint64_t genrand64_int64(struct theft_mt *r)
110 | {
111 | int i;
112 | uint64_t x;
113 | static uint64_t mag01[2]={0ULL, MATRIX_A};
114 |
115 | if (r->mti >= NN) { /* generate NN words at one time */
116 |
117 | /* if init has not been called, */
118 | /* a default initial seed is used */
119 | if (r->mti == NN+1)
120 | theft_mt_reset(r, 5489ULL);
121 |
122 | for (i=0;imt[i]&UM)|(r->mt[i+1]&LM);
124 | r->mt[i] = r->mt[i+MM] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
125 | }
126 | for (;imt[i]&UM)|(r->mt[i+1]&LM);
128 | r->mt[i] = r->mt[i+(MM-NN)] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
129 | }
130 | x = (r->mt[NN-1]&UM)|(r->mt[0]&LM);
131 | r->mt[NN-1] = r->mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
132 |
133 | r->mti = 0;
134 | }
135 |
136 | x = r->mt[r->mti++];
137 |
138 | x ^= (x >> 29) & 0x5555555555555555ULL;
139 | x ^= (x << 17) & 0x71D67FFFEDA60000ULL;
140 | x ^= (x << 37) & 0xFFF7EEE000000000ULL;
141 | x ^= (x >> 43);
142 |
143 | return x;
144 | }
145 |
--------------------------------------------------------------------------------
/deps/theft/theft_mt.h:
--------------------------------------------------------------------------------
1 | #ifndef THEFT_MT_H
2 | #define THEFT_MT_H
3 |
4 | #include
5 |
6 | /* Wrapper for Mersenne Twister.
7 | * See copyright and license in theft_mt.c, more details at:
8 | * http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
9 | *
10 | * The code has been modified to store internal state in heap/stack
11 | * allocated memory, rather than statically allocated memory, to allow
12 | * multiple instances running in the same address space. */
13 |
14 | #define THEFT_MT_PARAM_N 312
15 |
16 | struct theft_mt {
17 | uint64_t mt[THEFT_MT_PARAM_N]; /* the array for the state vector */
18 | int16_t mti;
19 | };
20 |
21 | /* Heap-allocate a mersenne twister struct. */
22 | struct theft_mt *theft_mt_init(uint64_t seed);
23 |
24 | /* Free a heap-allocated mersenne twister struct. */
25 | void theft_mt_free(struct theft_mt *mt);
26 |
27 | /* Reset a mersenne twister struct, possibly stack-allocated. */
28 | void theft_mt_reset(struct theft_mt *mt, uint64_t seed);
29 |
30 | /* Get a 64-bit random number. */
31 | uint64_t theft_mt_random(struct theft_mt *mt);
32 |
33 | /* Generate a random number on [0,1]-real-interval. */
34 | double theft_mt_random_double(struct theft_mt *mt);
35 |
36 | #endif
37 |
--------------------------------------------------------------------------------
/deps/theft/theft_types.h:
--------------------------------------------------------------------------------
1 | #ifndef THEFT_TYPES_H
2 | #define THEFT_TYPES_H
3 |
4 | /* A pseudo-random number/seed, used to generate instances. */
5 | typedef uint64_t theft_seed;
6 |
7 | /* A hash of an instance. */
8 | typedef uint64_t theft_hash;
9 |
10 | /* These are opaque, as far as the API is concerned. */
11 | struct theft_bloom; /* bloom filter */
12 | struct theft_mt; /* mersenne twister PRNG */
13 |
14 | /* Struct for property-testing state. */
15 | struct theft {
16 | FILE *out;
17 | theft_seed seed;
18 | uint8_t requested_bloom_bits;
19 | struct theft_bloom *bloom; /* bloom filter */
20 | struct theft_mt *mt; /* random number generator */
21 | };
22 |
23 | /* Special sentinel values returned instead of instance pointers. */
24 | #define THEFT_SKIP ((void *)-1)
25 | #define THEFT_ERROR ((void *)-2)
26 | #define THEFT_DEAD_END ((void *)-1)
27 | #define THEFT_NO_MORE_TACTICS ((void *)-3)
28 |
29 | /* Explicitly disable using the bloom filter.
30 | * Note that if you do this, you must be sure your simplify function
31 | * *always* returns a simpler value, or it will loop forever. */
32 | #define THEFT_BLOOM_DISABLE ((uint8_t)-1)
33 |
34 | /* Allocate and return an instance of the type, based on a known
35 | * pseudo-random number seed. To get additional seeds, use
36 | * theft_random(t); this stream of numbers will be deterministic, so if
37 | * the alloc callback is constructed appropriately, an identical
38 | * instance can be constructed later from the same initial seed.
39 | *
40 | * Returns a pointer to the instance, THEFT_ERROR, or THEFT_SKIP. */
41 | typedef void *(theft_alloc_cb)(struct theft *t, theft_seed seed, void *env);
42 |
43 | /* Free an instance. */
44 | typedef void (theft_free_cb)(void *instance, void *env);
45 |
46 | /* Hash an instance. Used to skip combinations of arguments which
47 | * have probably already been checked. */
48 | typedef theft_hash (theft_hash_cb)(void *instance, void *env);
49 |
50 | /* Attempt to shrink an instance to a simpler instance.
51 | *
52 | * For a given INSTANCE, there are likely to be multiple ways in which
53 | * it can be simplified. For example, a list of unsigned ints could have
54 | * the first element decremented, divided by 2, or dropped. This
55 | * callback should return a pointer to a freshly allocated, simplified
56 | * instance, or should return THEFT_DEAD_END to indicate that the
57 | * instance cannot be simplified further by this method.
58 | *
59 | * These tactics will be lazily explored breadth-first, to
60 | * try to find simpler versions of arguments that cause the
61 | * property to no longer hold.
62 | *
63 | * If this callback is NULL, it is equivalent to always returning
64 | * THEFT_NO_MORE_TACTICS. */
65 | typedef void *(theft_shrink_cb)(void *instance, uint32_t tactic, void *env);
66 |
67 | /* Print INSTANCE to output stream F.
68 | * Used for displaying counter-examples. Can be NULL. */
69 | typedef void (theft_print_cb)(FILE *f, void *instance, void *env);
70 |
71 | /* Result from a single trial. */
72 | typedef enum {
73 | THEFT_TRIAL_PASS, /* property held */
74 | THEFT_TRIAL_FAIL, /* property contradicted */
75 | THEFT_TRIAL_SKIP, /* user requested skip; N/A */
76 | THEFT_TRIAL_DUP, /* args probably already tried */
77 | THEFT_TRIAL_ERROR, /* unrecoverable error, halt */
78 | } theft_trial_res;
79 |
80 | /* A test property function. Arguments must match the types specified by
81 | * theft_cfg.type_info, or the result will be undefined. For example, a
82 | * propfun `prop_foo(A x, B y, C z)` must have a type_info array of
83 | * `{ info_A, info_B, info_C }`.
84 | *
85 | * Should return:
86 | * THEFT_TRIAL_PASS if the property holds,
87 | * THEFT_TRIAL_FAIL if a counter-example is found,
88 | * THEFT_TRIAL_SKIP if the combination of args isn't applicable,
89 | * or THEFT_TRIAL_ERROR if the whole run should be halted. */
90 | typedef theft_trial_res (theft_propfun)( /* arguments unconstrained */ );
91 |
92 | /* Callbacks used for testing with random instances of a type.
93 | * For more information, see comments on their typedefs. */
94 | struct theft_type_info {
95 | /* Required: */
96 | theft_alloc_cb *alloc; /* gen random instance from seed */
97 |
98 | /* Optional, but recommended: */
99 | theft_free_cb *free; /* free instance */
100 | theft_hash_cb *hash; /* instance -> hash */
101 | theft_shrink_cb *shrink; /* shrink instance */
102 | theft_print_cb *print; /* fprintf instance */
103 | };
104 |
105 | /* Result from an individual trial. */
106 | struct theft_trial_info {
107 | const char *name; /* property name */
108 | int trial; /* N'th trial */
109 | theft_seed seed; /* Seed used */
110 | theft_trial_res status; /* Run status */
111 | uint8_t arity; /* Number of arguments */
112 | void **args; /* Arguments used */
113 | };
114 |
115 | /* Whether to keep running trials after N failures/skips/etc. */
116 | typedef enum {
117 | THEFT_PROGRESS_CONTINUE, /* keep running trials */
118 | THEFT_PROGRESS_HALT, /* no need to continue */
119 | } theft_progress_callback_res;
120 |
121 | /* Handle test results.
122 | * Can be used to halt after too many failures, print '.' after
123 | * every N trials, etc. */
124 | typedef theft_progress_callback_res
125 | (theft_progress_cb)(struct theft_trial_info *info, void *env);
126 |
127 | /* Result from a trial run. */
128 | typedef enum {
129 | THEFT_RUN_PASS = 0, /* no failures */
130 | THEFT_RUN_FAIL = 1, /* 1 or more failures */
131 | THEFT_RUN_ERROR = 2, /* an error occurred */
132 | THEFT_RUN_ERROR_BAD_ARGS = -1, /* API misuse */
133 | /* Missing required callback for 1 or more types */
134 | THEFT_RUN_ERROR_MISSING_CALLBACK = -2,
135 | } theft_run_res;
136 |
137 | /* Optional report from a trial run; same meanings as theft_trial_res. */
138 | struct theft_trial_report {
139 | size_t pass;
140 | size_t fail;
141 | size_t skip;
142 | size_t dup;
143 | };
144 |
145 | /* Configuration struct for a theft test.
146 | * In C99, this struct can be specified as a literal, like this:
147 | *
148 | * struct theft_cfg cfg = {
149 | * .name = "example",
150 | * .fun = prop_fun,
151 | * .type_info = { type_arg_a, type_arg_b },
152 | * .seed = 0x7he5eed,
153 | * };
154 | *
155 | * and omitted fields will be set to defaults.
156 | * */
157 | struct theft_cfg {
158 | /* Property function under test, and info about its arguments.
159 | * The function is called with as many arguments are there
160 | * are values in TYPE_INFO, so it can crash if that is wrong. */
161 | theft_propfun *fun;
162 | struct theft_type_info *type_info[THEFT_MAX_ARITY];
163 |
164 | /* -- All fields after this point are optional. -- */
165 |
166 | /* Property name, displayed in test runner output. */
167 | const char *name;
168 |
169 | /* Array of seeds to always run, and its length.
170 | * Can be used for regression tests. */
171 | int always_seed_count; /* number of seeds */
172 | theft_seed *always_seeds; /* seeds to always run */
173 |
174 | /* Number of trials to run. Defaults to THEFT_DEF_TRIALS. */
175 | int trials;
176 |
177 | /* Progress callback, used to display progress in
178 | * slow-running tests, halt early under certain conditions, etc. */
179 | theft_progress_cb *progress_cb;
180 |
181 | /* Environment pointer. This is completely opaque to theft itself,
182 | * but will be passed along to all callbacks. */
183 | void *env;
184 |
185 | /* Struct to populate with more detailed test results. */
186 | struct theft_trial_report *report;
187 |
188 | /* Seed for the random number generator. */
189 | theft_seed seed;
190 | };
191 |
192 | /* Internal state for incremental hashing. */
193 | struct theft_hasher {
194 | theft_hash accum;
195 | };
196 |
197 | #endif
198 |
--------------------------------------------------------------------------------
/deps/theft/theft_types_internal.h:
--------------------------------------------------------------------------------
1 | #ifndef THEFT_TYPES_INTERNAL_H
2 | #define THEFT_TYPES_INTERNAL_H
3 |
4 | typedef struct theft theft;
5 |
6 | #define THEFT_MAX_TACTICS ((uint32_t)-1)
7 | #define DEFAULT_THEFT_SEED 0xa600d16b175eedL
8 |
9 | typedef enum {
10 | ALL_GEN_OK, /* all arguments generated okay */
11 | ALL_GEN_SKIP, /* skip due to user constraints */
12 | ALL_GEN_DUP, /* skip probably duplicated trial */
13 | ALL_GEN_ERROR, /* memory error or other failure */
14 | } all_gen_res_t;
15 |
16 | typedef enum {
17 | SHRINK_OK, /* simplified argument further */
18 | SHRINK_DEAD_END, /* at local minima */
19 | SHRINK_ERROR, /* hard error during shrinking */
20 | } shrink_res;
21 |
22 | /* Testing context for a specific property function. */
23 | struct theft_propfun_info {
24 | const char *name; /* property name, can be NULL */
25 | theft_propfun *fun; /* property function under test */
26 |
27 | /* Type info for ARITY arguments. */
28 | uint8_t arity; /* number of arguments */
29 | struct theft_type_info *type_info[THEFT_MAX_ARITY];
30 |
31 | /* Optional array of seeds to always run.
32 | * Can be used for regression tests. */
33 | int always_seed_count; /* number of seeds */
34 | theft_seed *always_seeds; /* seeds to always run */
35 | };
36 |
37 | static theft_trial_res
38 | call_fun(struct theft_propfun_info *info, void **args);
39 |
40 | static bool run_trial(struct theft *t, struct theft_propfun_info *info,
41 | void **args, theft_progress_cb *cb, void *env,
42 | struct theft_trial_report *r, struct theft_trial_info *ti,
43 | theft_progress_callback_res *cres);
44 |
45 | static void mark_called(theft *t, struct theft_propfun_info *info,
46 | void **args, void *env);
47 |
48 | static bool check_called(theft *t, struct theft_propfun_info *info,
49 | void **args, void *env);
50 |
51 | static theft_progress_callback_res report_on_failure(theft *t,
52 | struct theft_propfun_info *info,
53 | struct theft_trial_info *ti, theft_progress_cb *cb, void *env);
54 |
55 | static all_gen_res_t gen_all_args(theft *t, struct theft_propfun_info *info,
56 | theft_seed seed, void *args[THEFT_MAX_ARITY], void *env);
57 |
58 | static void free_args(struct theft_propfun_info *info,
59 | void **args, void *env);
60 |
61 | static theft_run_res theft_run_internal(struct theft *t,
62 | struct theft_propfun_info *info,
63 | int trials, theft_progress_cb *cb, void *env,
64 | struct theft_trial_report *r);
65 |
66 | static bool attempt_to_shrink(theft *t, struct theft_propfun_info *info,
67 | void *args[], void *env);
68 |
69 | static shrink_res
70 | attempt_to_shrink_arg(theft *t, struct theft_propfun_info *info,
71 | void *args[], void *env, int ai);
72 |
73 | #endif
74 |
--------------------------------------------------------------------------------
/fzy.1:
--------------------------------------------------------------------------------
1 | .TH FZY 1 "2018-09-23" "fzy 1.0"
2 | .SH NAME
3 | fzy \- A fuzzy text selector menu for the terminal.
4 | .SH SYNOPSIS
5 | .B fzy
6 | .IR [OPTION]...
7 | .SH DESCRIPTION
8 | .B fzy is a fuzzy text selector/file finder for the terminal using a search
9 | similar to that of TextMate or CmdT.
10 |
11 | fzy reads a list of newline-separated items from stdin to be displayed as a
12 | menu in the terminal.
13 | Upon pressing ENTER, the currently selected item is printed to stdout.
14 |
15 | Entering text narrows the items using fuzzy matching. Results are sorted using
16 | heuristics for the best match.
17 |
18 | .SH OPTIONS
19 | .TP
20 | .BR \-l ", " \-\-lines =\fILINES\fR
21 | How many lines of items to display. If unspecified, defaults to 10 lines.
22 | .
23 | .TP
24 | .BR \-p ", " \-\-prompt =\fIPROMPT\fR
25 | Input prompt (default: '> ')
26 | .
27 | .TP
28 | .BR \-s ", " \-\-show-scores
29 | Show the scores for each item.
30 | .
31 | .TP
32 | .BR \-t ", " \-\-tty =\fITTY\fR
33 | Use TTY instead of the default tty device (/dev/tty).
34 | .
35 | .TP
36 | .BR \-q ", " \-\-query =\fIQUERY\fR
37 | Use QUERY as the initial search query.
38 | .
39 | .TP
40 | .BR \-e ", " \-\-show-matches =\fIQUERY\fR
41 | Non-interactive mode. Print the matches in sorted order for QUERY to stdout.
42 | .
43 | .TP
44 | .BR \-0 ", " \-\-read-null
45 | Read input delimited by ASCII NUL characters.
46 | .
47 | .TP
48 | .BR \-h ", " \-\-help
49 | Usage help.
50 | .
51 | .TP
52 | .BR \-v ", " \-\-version
53 | Usage help.
54 | .
55 | .SH KEYS
56 | .
57 | .TP
58 | .BR "ENTER"
59 | Print the selected item to stdout and exit
60 | .TP
61 | .BR "Ctrl+c, Ctrl+g, Esc"
62 | Exit with status 1, without making a selection.
63 | .TP
64 | .BR "Up Arrow, Ctrl+p, Ctrl+k"
65 | Select the previous item
66 | .TP
67 | .BR "Down Arrow, Ctrl+n, Ctrl+j"
68 | Select the next item
69 | .TP
70 | Tab
71 | Replace the current search string with the selected item
72 | .TP
73 | .BR "Backspace, Ctrl+h"
74 | Delete the character before the cursor
75 | .TP
76 | .BR Ctrl+w
77 | Delete the word before the cursor
78 | .TP
79 | .BR Ctrl+u
80 | Delete the entire line
81 | .
82 | .SH USAGE EXAMPLES
83 | .
84 | .TP
85 | .BR "ls | fzy"
86 | Present a menu of items in the current directory
87 | .TP
88 | .BR "ls | fzy -l 25"
89 | Same as above, but show 25 lines of items
90 | .TP
91 | .BR "vi $(find -type f | fzy)"
92 | List files under the current directory and open the one selected in vi.
93 | .TP
94 | .BR "cd $(find -type d | fzy)"
95 | Present all directories under current path, and change to the one selected.
96 | .TP
97 | .BR "ps aux | fzy | awk '{ print $2 }' | xargs kill"
98 | List running processes, kill the selected process
99 | .TP
100 | .BR "git checkout $(git branch | cut -c 3- | fzy)"
101 | Same as above, but switching git branches.
102 | .SH AUTHOR
103 | John Hawthorn
104 |
--------------------------------------------------------------------------------
/src/bonus.h:
--------------------------------------------------------------------------------
1 | #ifndef BONUS_H
2 | #define BONUS_H BONUS_H
3 |
4 | #include "../config.h"
5 |
6 | #ifdef __cplusplus
7 | extern "C" {
8 | #endif
9 |
10 | #define ASSIGN_LOWER(v) \
11 | ['a'] = (v), \
12 | ['b'] = (v), \
13 | ['c'] = (v), \
14 | ['d'] = (v), \
15 | ['e'] = (v), \
16 | ['f'] = (v), \
17 | ['g'] = (v), \
18 | ['h'] = (v), \
19 | ['i'] = (v), \
20 | ['j'] = (v), \
21 | ['k'] = (v), \
22 | ['l'] = (v), \
23 | ['m'] = (v), \
24 | ['n'] = (v), \
25 | ['o'] = (v), \
26 | ['p'] = (v), \
27 | ['q'] = (v), \
28 | ['r'] = (v), \
29 | ['s'] = (v), \
30 | ['t'] = (v), \
31 | ['u'] = (v), \
32 | ['v'] = (v), \
33 | ['w'] = (v), \
34 | ['x'] = (v), \
35 | ['y'] = (v), \
36 | ['z'] = (v)
37 |
38 | #define ASSIGN_UPPER(v) \
39 | ['A'] = (v), \
40 | ['B'] = (v), \
41 | ['C'] = (v), \
42 | ['D'] = (v), \
43 | ['E'] = (v), \
44 | ['F'] = (v), \
45 | ['G'] = (v), \
46 | ['H'] = (v), \
47 | ['I'] = (v), \
48 | ['J'] = (v), \
49 | ['K'] = (v), \
50 | ['L'] = (v), \
51 | ['M'] = (v), \
52 | ['N'] = (v), \
53 | ['O'] = (v), \
54 | ['P'] = (v), \
55 | ['Q'] = (v), \
56 | ['R'] = (v), \
57 | ['S'] = (v), \
58 | ['T'] = (v), \
59 | ['U'] = (v), \
60 | ['V'] = (v), \
61 | ['W'] = (v), \
62 | ['X'] = (v), \
63 | ['Y'] = (v), \
64 | ['Z'] = (v)
65 |
66 | #define ASSIGN_DIGIT(v) \
67 | ['0'] = (v), \
68 | ['1'] = (v), \
69 | ['2'] = (v), \
70 | ['3'] = (v), \
71 | ['4'] = (v), \
72 | ['5'] = (v), \
73 | ['6'] = (v), \
74 | ['7'] = (v), \
75 | ['8'] = (v), \
76 | ['9'] = (v)
77 |
78 | const score_t bonus_states[3][256] = {
79 | { 0 },
80 | {
81 | ['/'] = SCORE_MATCH_SLASH,
82 | ['-'] = SCORE_MATCH_WORD,
83 | ['_'] = SCORE_MATCH_WORD,
84 | [' '] = SCORE_MATCH_WORD,
85 | ['.'] = SCORE_MATCH_DOT,
86 | },
87 | {
88 | ['/'] = SCORE_MATCH_SLASH,
89 | ['-'] = SCORE_MATCH_WORD,
90 | ['_'] = SCORE_MATCH_WORD,
91 | [' '] = SCORE_MATCH_WORD,
92 | ['.'] = SCORE_MATCH_DOT,
93 |
94 | /* ['a' ... 'z'] = SCORE_MATCH_CAPITAL, */
95 | ASSIGN_LOWER(SCORE_MATCH_CAPITAL)
96 | }
97 | };
98 |
99 | const size_t bonus_index[256] = {
100 | /* ['A' ... 'Z'] = 2 */
101 | ASSIGN_UPPER(2),
102 |
103 | /* ['a' ... 'z'] = 1 */
104 | ASSIGN_LOWER(1),
105 |
106 | /* ['0' ... '9'] = 1 */
107 | ASSIGN_DIGIT(1)
108 | };
109 |
110 | #define COMPUTE_BONUS(last_ch, ch) (bonus_states[bonus_index[(unsigned char)(ch)]][(unsigned char)(last_ch)])
111 |
112 | #ifdef __cplusplus
113 | }
114 | #endif
115 |
116 | #endif
117 |
--------------------------------------------------------------------------------
/src/choices.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include "options.h"
9 | #include "choices.h"
10 | #include "match.h"
11 |
12 | /* Initial size of buffer for storing input in memory */
13 | #define INITIAL_BUFFER_CAPACITY 4096
14 |
15 | /* Initial size of choices array */
16 | #define INITIAL_CHOICE_CAPACITY 128
17 |
18 | static int cmpchoice(const void *_idx1, const void *_idx2) {
19 | const struct scored_result *a = _idx1;
20 | const struct scored_result *b = _idx2;
21 |
22 | if (a->score == b->score) {
23 | /* To ensure a stable sort, we must also sort by the string
24 | * pointers. We can do this since we know all the strings are
25 | * from a contiguous memory segment (buffer in choices_t).
26 | */
27 | if (a->str < b->str) {
28 | return -1;
29 | } else {
30 | return 1;
31 | }
32 | } else if (a->score < b->score) {
33 | return 1;
34 | } else {
35 | return -1;
36 | }
37 | }
38 |
39 | static void *safe_realloc(void *buffer, size_t size) {
40 | buffer = realloc(buffer, size);
41 | if (!buffer) {
42 | fprintf(stderr, "Error: Can't allocate memory (%zu bytes)\n", size);
43 | abort();
44 | }
45 |
46 | return buffer;
47 | }
48 |
49 | void choices_fread(choices_t *c, FILE *file, char input_delimiter) {
50 | /* Save current position for parsing later */
51 | size_t buffer_start = c->buffer_size;
52 |
53 | /* Resize buffer to at least one byte more capacity than our current
54 | * size. This uses a power of two of INITIAL_BUFFER_CAPACITY.
55 | * This must work even when c->buffer is NULL and c->buffer_size is 0
56 | */
57 | size_t capacity = INITIAL_BUFFER_CAPACITY;
58 | while (capacity <= c->buffer_size)
59 | capacity *= 2;
60 | c->buffer = safe_realloc(c->buffer, capacity);
61 |
62 | /* Continue reading until we get a "short" read, indicating EOF */
63 | while ((c->buffer_size += fread(c->buffer + c->buffer_size, 1, capacity - c->buffer_size, file)) == capacity) {
64 | capacity *= 2;
65 | c->buffer = safe_realloc(c->buffer, capacity);
66 | }
67 | c->buffer = safe_realloc(c->buffer, c->buffer_size + 1);
68 | c->buffer[c->buffer_size++] = '\0';
69 |
70 | /* Truncate buffer to used size, (maybe) freeing some memory for
71 | * future allocations.
72 | */
73 |
74 | /* Tokenize input and add to choices */
75 | const char *line_end = c->buffer + c->buffer_size;
76 | char *line = c->buffer + buffer_start;
77 | do {
78 | char *nl = strchr(line, input_delimiter);
79 | if (nl)
80 | *nl++ = '\0';
81 |
82 | /* Skip empty lines */
83 | if (*line)
84 | choices_add(c, line);
85 |
86 | line = nl;
87 | } while (line && line < line_end);
88 | }
89 |
90 | static void choices_resize(choices_t *c, size_t new_capacity) {
91 | c->strings = safe_realloc(c->strings, new_capacity * sizeof(const char *));
92 | c->capacity = new_capacity;
93 | }
94 |
95 | static void choices_reset_search(choices_t *c) {
96 | free(c->results);
97 | c->selection = c->available = 0;
98 | c->results = NULL;
99 | }
100 |
101 | void choices_init(choices_t *c, options_t *options) {
102 | c->strings = NULL;
103 | c->results = NULL;
104 |
105 | c->buffer_size = 0;
106 | c->buffer = NULL;
107 |
108 | c->capacity = c->size = 0;
109 | choices_resize(c, INITIAL_CHOICE_CAPACITY);
110 |
111 | if (options->workers) {
112 | c->worker_count = options->workers;
113 | } else {
114 | c->worker_count = (int)sysconf(_SC_NPROCESSORS_ONLN);
115 | }
116 |
117 | choices_reset_search(c);
118 | }
119 |
120 | void choices_destroy(choices_t *c) {
121 | free(c->buffer);
122 | c->buffer = NULL;
123 | c->buffer_size = 0;
124 |
125 | free(c->strings);
126 | c->strings = NULL;
127 | c->capacity = c->size = 0;
128 |
129 | free(c->results);
130 | c->results = NULL;
131 | c->available = c->selection = 0;
132 | }
133 |
134 | void choices_add(choices_t *c, const char *choice) {
135 | /* Previous search is now invalid */
136 | choices_reset_search(c);
137 |
138 | if (c->size == c->capacity) {
139 | choices_resize(c, c->capacity * 2);
140 | }
141 | c->strings[c->size++] = choice;
142 | }
143 |
144 | size_t choices_available(choices_t *c) {
145 | return c->available;
146 | }
147 |
148 | #define BATCH_SIZE 512
149 |
150 | struct result_list {
151 | struct scored_result *list;
152 | size_t size;
153 | };
154 |
155 | struct search_job {
156 | pthread_mutex_t lock;
157 | choices_t *choices;
158 | const char *search;
159 | size_t processed;
160 | struct worker *workers;
161 | };
162 |
163 | struct worker {
164 | pthread_t thread_id;
165 | struct search_job *job;
166 | unsigned int worker_num;
167 | struct result_list result;
168 | };
169 |
170 | static void worker_get_next_batch(struct search_job *job, size_t *start, size_t *end) {
171 | pthread_mutex_lock(&job->lock);
172 |
173 | *start = job->processed;
174 |
175 | job->processed += BATCH_SIZE;
176 | if (job->processed > job->choices->size) {
177 | job->processed = job->choices->size;
178 | }
179 |
180 | *end = job->processed;
181 |
182 | pthread_mutex_unlock(&job->lock);
183 | }
184 |
185 | static struct result_list merge2(struct result_list list1, struct result_list list2) {
186 | size_t result_index = 0, index1 = 0, index2 = 0;
187 |
188 | struct result_list result;
189 | result.size = list1.size + list2.size;
190 | result.list = malloc(result.size * sizeof(struct scored_result));
191 | if (!result.list) {
192 | fprintf(stderr, "Error: Can't allocate memory\n");
193 | abort();
194 | }
195 |
196 | while(index1 < list1.size && index2 < list2.size) {
197 | if (cmpchoice(&list1.list[index1], &list2.list[index2]) < 0) {
198 | result.list[result_index++] = list1.list[index1++];
199 | } else {
200 | result.list[result_index++] = list2.list[index2++];
201 | }
202 | }
203 |
204 | while(index1 < list1.size) {
205 | result.list[result_index++] = list1.list[index1++];
206 | }
207 | while(index2 < list2.size) {
208 | result.list[result_index++] = list2.list[index2++];
209 | }
210 |
211 | free(list1.list);
212 | free(list2.list);
213 |
214 | return result;
215 | }
216 |
217 | static void *choices_search_worker(void *data) {
218 | struct worker *w = (struct worker *)data;
219 | struct search_job *job = w->job;
220 | const choices_t *c = job->choices;
221 | struct result_list *result = &w->result;
222 |
223 | size_t start, end;
224 |
225 | for(;;) {
226 | worker_get_next_batch(job, &start, &end);
227 |
228 | if(start == end) {
229 | break;
230 | }
231 |
232 | for(size_t i = start; i < end; i++) {
233 | if (has_match(job->search, c->strings[i])) {
234 | result->list[result->size].str = c->strings[i];
235 | result->list[result->size].score = match(job->search, c->strings[i]);
236 | result->size++;
237 | }
238 | }
239 | }
240 |
241 | /* Sort the partial result */
242 | qsort(result->list, result->size, sizeof(struct scored_result), cmpchoice);
243 |
244 | /* Fan-in, merging results */
245 | for(unsigned int step = 0;; step++) {
246 | if (w->worker_num % (2 << step))
247 | break;
248 |
249 | unsigned int next_worker = w->worker_num | (1 << step);
250 | if (next_worker >= c->worker_count)
251 | break;
252 |
253 | if ((errno = pthread_join(job->workers[next_worker].thread_id, NULL))) {
254 | perror("pthread_join");
255 | exit(EXIT_FAILURE);
256 | }
257 |
258 | w->result = merge2(w->result, job->workers[next_worker].result);
259 | }
260 |
261 | return NULL;
262 | }
263 |
264 | void choices_search(choices_t *c, const char *search) {
265 | choices_reset_search(c);
266 |
267 | struct search_job *job = calloc(1, sizeof(struct search_job));
268 | job->search = search;
269 | job->choices = c;
270 | if (pthread_mutex_init(&job->lock, NULL) != 0) {
271 | fprintf(stderr, "Error: pthread_mutex_init failed\n");
272 | abort();
273 | }
274 | job->workers = calloc(c->worker_count, sizeof(struct worker));
275 |
276 | struct worker *workers = job->workers;
277 | for (int i = c->worker_count - 1; i >= 0; i--) {
278 | workers[i].job = job;
279 | workers[i].worker_num = i;
280 | workers[i].result.size = 0;
281 | workers[i].result.list = malloc(c->size * sizeof(struct scored_result)); /* FIXME: This is overkill */
282 |
283 | /* These must be created last-to-first to avoid a race condition when fanning in */
284 | if ((errno = pthread_create(&workers[i].thread_id, NULL, &choices_search_worker, &workers[i]))) {
285 | perror("pthread_create");
286 | exit(EXIT_FAILURE);
287 | }
288 | }
289 |
290 | if (pthread_join(workers[0].thread_id, NULL)) {
291 | perror("pthread_join");
292 | exit(EXIT_FAILURE);
293 | }
294 |
295 | c->results = workers[0].result.list;
296 | c->available = workers[0].result.size;
297 |
298 | free(workers);
299 | pthread_mutex_destroy(&job->lock);
300 | free(job);
301 | }
302 |
303 | const char *choices_get(choices_t *c, size_t n) {
304 | if (n < c->available) {
305 | return c->results[n].str;
306 | } else {
307 | return NULL;
308 | }
309 | }
310 |
311 | score_t choices_getscore(choices_t *c, size_t n) {
312 | return c->results[n].score;
313 | }
314 |
315 | void choices_prev(choices_t *c) {
316 | if (c->available)
317 | c->selection = (c->selection + c->available - 1) % c->available;
318 | }
319 |
320 | void choices_next(choices_t *c) {
321 | if (c->available)
322 | c->selection = (c->selection + 1) % c->available;
323 | }
324 |
--------------------------------------------------------------------------------
/src/choices.h:
--------------------------------------------------------------------------------
1 | #ifndef CHOICES_H
2 | #define CHOICES_H CHOICES_H
3 |
4 | #include
5 |
6 | #include "match.h"
7 | #include "options.h"
8 |
9 | #ifdef __cplusplus
10 | extern "C" {
11 | #endif
12 |
13 | struct scored_result {
14 | score_t score;
15 | const char *str;
16 | };
17 |
18 | typedef struct {
19 | char *buffer;
20 | size_t buffer_size;
21 |
22 | size_t capacity;
23 | size_t size;
24 |
25 | const char **strings;
26 | struct scored_result *results;
27 |
28 | size_t available;
29 | size_t selection;
30 |
31 | unsigned int worker_count;
32 | } choices_t;
33 |
34 | void choices_init(choices_t *c, options_t *options);
35 | void choices_fread(choices_t *c, FILE *file, char input_delimiter);
36 | void choices_destroy(choices_t *c);
37 | void choices_add(choices_t *c, const char *choice);
38 | size_t choices_available(choices_t *c);
39 | void choices_search(choices_t *c, const char *search);
40 | const char *choices_get(choices_t *c, size_t n);
41 | score_t choices_getscore(choices_t *c, size_t n);
42 | void choices_prev(choices_t *c);
43 | void choices_next(choices_t *c);
44 |
45 | #ifdef __cplusplus
46 | }
47 | #endif
48 |
49 | #endif
50 |
--------------------------------------------------------------------------------
/src/config.def.h:
--------------------------------------------------------------------------------
1 | #ifdef __cplusplus
2 | extern "C" {
3 | #endif
4 |
5 | #define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW
6 |
7 | #define SCORE_GAP_LEADING -0.005
8 | #define SCORE_GAP_TRAILING -0.005
9 | #define SCORE_GAP_INNER -0.01
10 | #define SCORE_MATCH_CONSECUTIVE 1.0
11 | #define SCORE_MATCH_SLASH 0.9
12 | #define SCORE_MATCH_WORD 0.8
13 | #define SCORE_MATCH_CAPITAL 0.7
14 | #define SCORE_MATCH_DOT 0.6
15 |
16 | /* Time (in ms) to wait for additional bytes of an escape sequence */
17 | #define KEYTIMEOUT 25
18 |
19 | #define DEFAULT_TTY "/dev/tty"
20 | #define DEFAULT_PROMPT "> "
21 | #define DEFAULT_NUM_LINES 10
22 | #define DEFAULT_WORKERS 0
23 | #define DEFAULT_SHOW_INFO 0
24 |
25 | #ifdef __cplusplus
26 | }
27 | #endif
28 |
--------------------------------------------------------------------------------
/src/fzy.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include "match.h"
9 | #include "tty.h"
10 | #include "choices.h"
11 | #include "options.h"
12 | #include "tty_interface.h"
13 |
14 | #include "../config.h"
15 |
16 | int main(int argc, char *argv[]) {
17 | int ret = 0;
18 |
19 | options_t options;
20 | options_parse(&options, argc, argv);
21 |
22 | choices_t choices;
23 | choices_init(&choices, &options);
24 |
25 | if (options.benchmark) {
26 | if (!options.filter) {
27 | fprintf(stderr, "Must specify -e/--show-matches with --benchmark\n");
28 | exit(EXIT_FAILURE);
29 | }
30 | choices_fread(&choices, stdin, options.input_delimiter);
31 | for (int i = 0; i < options.benchmark; i++)
32 | choices_search(&choices, options.filter);
33 | } else if (options.filter) {
34 | choices_fread(&choices, stdin, options.input_delimiter);
35 | choices_search(&choices, options.filter);
36 | for (size_t i = 0; i < choices_available(&choices); i++) {
37 | if (options.show_scores)
38 | printf("%f\t", choices_getscore(&choices, i));
39 | printf("%s\n", choices_get(&choices, i));
40 | }
41 | } else {
42 | /* interactive */
43 |
44 | if (isatty(STDIN_FILENO))
45 | choices_fread(&choices, stdin, options.input_delimiter);
46 |
47 | tty_t tty;
48 | tty_init(&tty, options.tty_filename);
49 |
50 | if (!isatty(STDIN_FILENO))
51 | choices_fread(&choices, stdin, options.input_delimiter);
52 |
53 | if (options.num_lines > choices.size)
54 | options.num_lines = choices.size;
55 |
56 | int num_lines_adjustment = 1;
57 | if (options.show_info)
58 | num_lines_adjustment++;
59 |
60 | if (options.num_lines + num_lines_adjustment > tty_getheight(&tty))
61 | options.num_lines = tty_getheight(&tty) - num_lines_adjustment;
62 |
63 | tty_interface_t tty_interface;
64 | tty_interface_init(&tty_interface, &tty, &choices, &options);
65 | ret = tty_interface_run(&tty_interface);
66 | }
67 |
68 | choices_destroy(&choices);
69 |
70 | return ret;
71 | }
72 |
--------------------------------------------------------------------------------
/src/match.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "match.h"
10 | #include "bonus.h"
11 |
12 | #include "../config.h"
13 |
14 | char *strcasechr(const char *s, char c) {
15 | const char accept[3] = {c, toupper(c), 0};
16 | return strpbrk(s, accept);
17 | }
18 |
19 | int has_match(const char *needle, const char *haystack) {
20 | while (*needle) {
21 | char nch = *needle++;
22 |
23 | if (!(haystack = strcasechr(haystack, nch))) {
24 | return 0;
25 | }
26 | haystack++;
27 | }
28 | return 1;
29 | }
30 |
31 | #define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0)
32 |
33 | #define max(a, b) (((a) > (b)) ? (a) : (b))
34 |
35 | struct match_struct {
36 | int needle_len;
37 | int haystack_len;
38 |
39 | char lower_needle[MATCH_MAX_LEN];
40 | char lower_haystack[MATCH_MAX_LEN];
41 |
42 | score_t match_bonus[MATCH_MAX_LEN];
43 | };
44 |
45 | static void precompute_bonus(const char *haystack, score_t *match_bonus) {
46 | /* Which positions are beginning of words */
47 | char last_ch = '/';
48 | for (int i = 0; haystack[i]; i++) {
49 | char ch = haystack[i];
50 | match_bonus[i] = COMPUTE_BONUS(last_ch, ch);
51 | last_ch = ch;
52 | }
53 | }
54 |
55 | static void setup_match_struct(struct match_struct *match, const char *needle, const char *haystack) {
56 | match->needle_len = strlen(needle);
57 | match->haystack_len = strlen(haystack);
58 |
59 | if (match->haystack_len > MATCH_MAX_LEN || match->needle_len > match->haystack_len) {
60 | return;
61 | }
62 |
63 | for (int i = 0; i < match->needle_len; i++)
64 | match->lower_needle[i] = tolower(needle[i]);
65 |
66 | for (int i = 0; i < match->haystack_len; i++)
67 | match->lower_haystack[i] = tolower(haystack[i]);
68 |
69 | precompute_bonus(haystack, match->match_bonus);
70 | }
71 |
72 | static inline void match_row(const struct match_struct *match, int row, score_t *curr_D, score_t *curr_M, const score_t *last_D, const score_t *last_M) {
73 | int n = match->needle_len;
74 | int m = match->haystack_len;
75 | int i = row;
76 |
77 | const char *lower_needle = match->lower_needle;
78 | const char *lower_haystack = match->lower_haystack;
79 | const score_t *match_bonus = match->match_bonus;
80 |
81 | score_t prev_score = SCORE_MIN;
82 | score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER;
83 |
84 | for (int j = 0; j < m; j++) {
85 | if (lower_needle[i] == lower_haystack[j]) {
86 | score_t score = SCORE_MIN;
87 | if (!i) {
88 | score = (j * SCORE_GAP_LEADING) + match_bonus[j];
89 | } else if (j) { /* i > 0 && j > 0*/
90 | score = max(
91 | last_M[j - 1] + match_bonus[j],
92 |
93 | /* consecutive match, doesn't stack with match_bonus */
94 | last_D[j - 1] + SCORE_MATCH_CONSECUTIVE);
95 | }
96 | curr_D[j] = score;
97 | curr_M[j] = prev_score = max(score, prev_score + gap_score);
98 | } else {
99 | curr_D[j] = SCORE_MIN;
100 | curr_M[j] = prev_score = prev_score + gap_score;
101 | }
102 | }
103 | }
104 |
105 | score_t match(const char *needle, const char *haystack) {
106 | if (!*needle)
107 | return SCORE_MIN;
108 |
109 | struct match_struct match;
110 | setup_match_struct(&match, needle, haystack);
111 |
112 | int n = match.needle_len;
113 | int m = match.haystack_len;
114 |
115 | if (m > MATCH_MAX_LEN || n > m) {
116 | /*
117 | * Unreasonably large candidate: return no score
118 | * If it is a valid match it will still be returned, it will
119 | * just be ranked below any reasonably sized candidates
120 | */
121 | return SCORE_MIN;
122 | } else if (n == m) {
123 | /* Since this method can only be called with a haystack which
124 | * matches needle. If the lengths of the strings are equal the
125 | * strings themselves must also be equal (ignoring case).
126 | */
127 | return SCORE_MAX;
128 | }
129 |
130 | /*
131 | * D[][] Stores the best score for this position ending with a match.
132 | * M[][] Stores the best possible score at this position.
133 | */
134 | score_t D[2][MATCH_MAX_LEN], M[2][MATCH_MAX_LEN];
135 |
136 | score_t *last_D, *last_M;
137 | score_t *curr_D, *curr_M;
138 |
139 | last_D = D[0];
140 | last_M = M[0];
141 | curr_D = D[1];
142 | curr_M = M[1];
143 |
144 | for (int i = 0; i < n; i++) {
145 | match_row(&match, i, curr_D, curr_M, last_D, last_M);
146 |
147 | SWAP(curr_D, last_D, score_t *);
148 | SWAP(curr_M, last_M, score_t *);
149 | }
150 |
151 | return last_M[m - 1];
152 | }
153 |
154 | score_t match_positions(const char *needle, const char *haystack, size_t *positions) {
155 | if (!*needle)
156 | return SCORE_MIN;
157 |
158 | struct match_struct match;
159 | setup_match_struct(&match, needle, haystack);
160 |
161 | int n = match.needle_len;
162 | int m = match.haystack_len;
163 |
164 | if (m > MATCH_MAX_LEN || n > m) {
165 | /*
166 | * Unreasonably large candidate: return no score
167 | * If it is a valid match it will still be returned, it will
168 | * just be ranked below any reasonably sized candidates
169 | */
170 | return SCORE_MIN;
171 | } else if (n == m) {
172 | /* Since this method can only be called with a haystack which
173 | * matches needle. If the lengths of the strings are equal the
174 | * strings themselves must also be equal (ignoring case).
175 | */
176 | if (positions)
177 | for (int i = 0; i < n; i++)
178 | positions[i] = i;
179 | return SCORE_MAX;
180 | }
181 |
182 | /*
183 | * D[][] Stores the best score for this position ending with a match.
184 | * M[][] Stores the best possible score at this position.
185 | */
186 | score_t (*D)[MATCH_MAX_LEN], (*M)[MATCH_MAX_LEN];
187 | M = malloc(sizeof(score_t) * MATCH_MAX_LEN * n);
188 | D = malloc(sizeof(score_t) * MATCH_MAX_LEN * n);
189 |
190 | score_t *last_D = NULL, *last_M = NULL;
191 | score_t *curr_D, *curr_M;
192 |
193 | for (int i = 0; i < n; i++) {
194 | curr_D = &D[i][0];
195 | curr_M = &M[i][0];
196 |
197 | match_row(&match, i, curr_D, curr_M, last_D, last_M);
198 |
199 | last_D = curr_D;
200 | last_M = curr_M;
201 | }
202 |
203 | /* backtrace to find the positions of optimal matching */
204 | if (positions) {
205 | int match_required = 0;
206 | for (int i = n - 1, j = m - 1; i >= 0; i--) {
207 | for (; j >= 0; j--) {
208 | /*
209 | * There may be multiple paths which result in
210 | * the optimal weight.
211 | *
212 | * For simplicity, we will pick the first one
213 | * we encounter, the latest in the candidate
214 | * string.
215 | */
216 | if (D[i][j] != SCORE_MIN &&
217 | (match_required || D[i][j] == M[i][j])) {
218 | /* If this score was determined using
219 | * SCORE_MATCH_CONSECUTIVE, the
220 | * previous character MUST be a match
221 | */
222 | match_required =
223 | i && j &&
224 | M[i][j] == D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE;
225 | positions[i] = j--;
226 | break;
227 | }
228 | }
229 | }
230 | }
231 |
232 | score_t result = M[n - 1][m - 1];
233 |
234 | free(M);
235 | free(D);
236 |
237 | return result;
238 | }
239 |
--------------------------------------------------------------------------------
/src/match.h:
--------------------------------------------------------------------------------
1 | #ifndef MATCH_H
2 | #define MATCH_H MATCH_H
3 |
4 | #include
5 |
6 | #ifdef __cplusplus
7 | extern "C" {
8 | #endif
9 |
10 | typedef double score_t;
11 | #define SCORE_MAX INFINITY
12 | #define SCORE_MIN -INFINITY
13 |
14 | #define MATCH_MAX_LEN 1024
15 |
16 | int has_match(const char *needle, const char *haystack);
17 | score_t match_positions(const char *needle, const char *haystack, size_t *positions);
18 | score_t match(const char *needle, const char *haystack);
19 |
20 | #ifdef __cplusplus
21 | }
22 | #endif
23 |
24 | #endif
25 |
--------------------------------------------------------------------------------
/src/options.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #include "options.h"
8 |
9 | #include "../config.h"
10 |
11 | static const char *usage_str =
12 | ""
13 | "Usage: fzy [OPTION]...\n"
14 | " -l, --lines=LINES Specify how many lines of results to show (default 10)\n"
15 | " -p, --prompt=PROMPT Input prompt (default '> ')\n"
16 | " -q, --query=QUERY Use QUERY as the initial search string\n"
17 | " -e, --show-matches=QUERY Output the sorted matches of QUERY\n"
18 | " -t, --tty=TTY Specify file to use as TTY device (default /dev/tty)\n"
19 | " -s, --show-scores Show the scores of each match\n"
20 | " -0, --read-null Read input delimited by ASCII NUL characters\n"
21 | " -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)\n"
22 | " -i, --show-info Show selection info line\n"
23 | " -h, --help Display this help and exit\n"
24 | " -v, --version Output version information and exit\n";
25 |
26 | static void usage(const char *argv0) {
27 | fprintf(stderr, usage_str, argv0);
28 | }
29 |
30 | static struct option longopts[] = {{"show-matches", required_argument, NULL, 'e'},
31 | {"query", required_argument, NULL, 'q'},
32 | {"lines", required_argument, NULL, 'l'},
33 | {"tty", required_argument, NULL, 't'},
34 | {"prompt", required_argument, NULL, 'p'},
35 | {"show-scores", no_argument, NULL, 's'},
36 | {"read-null", no_argument, NULL, '0'},
37 | {"version", no_argument, NULL, 'v'},
38 | {"benchmark", optional_argument, NULL, 'b'},
39 | {"workers", required_argument, NULL, 'j'},
40 | {"show-info", no_argument, NULL, 'i'},
41 | {"help", no_argument, NULL, 'h'},
42 | {NULL, 0, NULL, 0}};
43 |
44 | void options_init(options_t *options) {
45 | /* set defaults */
46 | options->benchmark = 0;
47 | options->filter = NULL;
48 | options->init_search = NULL;
49 | options->show_scores = 0;
50 | options->scrolloff = 1;
51 | options->tty_filename = DEFAULT_TTY;
52 | options->num_lines = DEFAULT_NUM_LINES;
53 | options->prompt = DEFAULT_PROMPT;
54 | options->workers = DEFAULT_WORKERS;
55 | options->input_delimiter = '\n';
56 | options->show_info = DEFAULT_SHOW_INFO;
57 | }
58 |
59 | void options_parse(options_t *options, int argc, char *argv[]) {
60 | options_init(options);
61 |
62 | int c;
63 | while ((c = getopt_long(argc, argv, "vhs0e:q:l:t:p:j:i", longopts, NULL)) != -1) {
64 | switch (c) {
65 | case 'v':
66 | printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]);
67 | exit(EXIT_SUCCESS);
68 | case 's':
69 | options->show_scores = 1;
70 | break;
71 | case '0':
72 | options->input_delimiter = '\0';
73 | break;
74 | case 'q':
75 | options->init_search = optarg;
76 | break;
77 | case 'e':
78 | options->filter = optarg;
79 | break;
80 | case 'b':
81 | if (optarg) {
82 | if (sscanf(optarg, "%d", &options->benchmark) != 1) {
83 | usage(argv[0]);
84 | exit(EXIT_FAILURE);
85 | }
86 | } else {
87 | options->benchmark = 100;
88 | }
89 | break;
90 | case 't':
91 | options->tty_filename = optarg;
92 | break;
93 | case 'p':
94 | options->prompt = optarg;
95 | break;
96 | case 'j':
97 | if (sscanf(optarg, "%u", &options->workers) != 1) {
98 | usage(argv[0]);
99 | exit(EXIT_FAILURE);
100 | }
101 | break;
102 | case 'l': {
103 | int l;
104 | if (!strcmp(optarg, "max")) {
105 | l = INT_MAX;
106 | } else if (sscanf(optarg, "%d", &l) != 1 || l < 3) {
107 | fprintf(stderr, "Invalid format for --lines: %s\n", optarg);
108 | fprintf(stderr, "Must be integer in range 3..\n");
109 | usage(argv[0]);
110 | exit(EXIT_FAILURE);
111 | }
112 | options->num_lines = l;
113 | } break;
114 | case 'i':
115 | options->show_info = 1;
116 | break;
117 | case 'h':
118 | default:
119 | usage(argv[0]);
120 | exit(EXIT_SUCCESS);
121 | }
122 | }
123 | if (optind != argc) {
124 | usage(argv[0]);
125 | exit(EXIT_FAILURE);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/options.h:
--------------------------------------------------------------------------------
1 | #ifndef OPTIONS_H
2 | #define OPTIONS_H OPTIONS_H
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 |
8 | typedef struct {
9 | int benchmark;
10 | const char *filter;
11 | const char *init_search;
12 | const char *tty_filename;
13 | int show_scores;
14 | unsigned int num_lines;
15 | unsigned int scrolloff;
16 | const char *prompt;
17 | unsigned int workers;
18 | char input_delimiter;
19 | int show_info;
20 | } options_t;
21 |
22 | void options_init(options_t *options);
23 | void options_parse(options_t *options, int argc, char *argv[]);
24 |
25 | #ifdef __cplusplus
26 | }
27 | #endif
28 |
29 | #endif
30 |
--------------------------------------------------------------------------------
/src/tty.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "tty.h"
12 |
13 | #include "../config.h"
14 |
15 | void tty_reset(tty_t *tty) {
16 | tcsetattr(tty->fdin, TCSANOW, &tty->original_termios);
17 | }
18 |
19 | void tty_close(tty_t *tty) {
20 | tty_reset(tty);
21 | fclose(tty->fout);
22 | close(tty->fdin);
23 | }
24 |
25 | static void handle_sigwinch(int sig){
26 | (void)sig;
27 | }
28 |
29 | void tty_init(tty_t *tty, const char *tty_filename) {
30 | tty->fdin = open(tty_filename, O_RDONLY);
31 | if (tty->fdin < 0) {
32 | perror("Failed to open tty");
33 | exit(EXIT_FAILURE);
34 | }
35 |
36 | tty->fout = fopen(tty_filename, "w");
37 | if (!tty->fout) {
38 | perror("Failed to open tty");
39 | exit(EXIT_FAILURE);
40 | }
41 |
42 | if (setvbuf(tty->fout, NULL, _IOFBF, 4096)) {
43 | perror("setvbuf");
44 | exit(EXIT_FAILURE);
45 | }
46 |
47 | if (tcgetattr(tty->fdin, &tty->original_termios)) {
48 | perror("tcgetattr");
49 | exit(EXIT_FAILURE);
50 | }
51 |
52 | struct termios new_termios = tty->original_termios;
53 |
54 | /*
55 | * Disable all of
56 | * ICANON Canonical input (erase and kill processing).
57 | * ECHO Echo.
58 | * ISIG Signals from control characters
59 | * ICRNL Conversion of CR characters into NL
60 | */
61 | new_termios.c_iflag &= ~(ICRNL);
62 | new_termios.c_lflag &= ~(ICANON | ECHO | ISIG);
63 |
64 | if (tcsetattr(tty->fdin, TCSANOW, &new_termios))
65 | perror("tcsetattr");
66 |
67 | tty_getwinsz(tty);
68 |
69 | tty_setnormal(tty);
70 |
71 | signal(SIGWINCH, handle_sigwinch);
72 | }
73 |
74 | void tty_getwinsz(tty_t *tty) {
75 | struct winsize ws;
76 | if (ioctl(fileno(tty->fout), TIOCGWINSZ, &ws) == -1) {
77 | tty->maxwidth = 80;
78 | tty->maxheight = 25;
79 | } else {
80 | tty->maxwidth = ws.ws_col;
81 | tty->maxheight = ws.ws_row;
82 | }
83 | }
84 |
85 | char tty_getchar(tty_t *tty) {
86 | char ch;
87 | int size = read(tty->fdin, &ch, 1);
88 | if (size < 0) {
89 | perror("error reading from tty");
90 | exit(EXIT_FAILURE);
91 | } else if (size == 0) {
92 | /* EOF */
93 | exit(EXIT_FAILURE);
94 | } else {
95 | return ch;
96 | }
97 | }
98 |
99 | int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal) {
100 | fd_set readfs;
101 | FD_ZERO(&readfs);
102 | FD_SET(tty->fdin, &readfs);
103 |
104 | struct timespec ts = {timeout / 1000, (timeout % 1000) * 1000000};
105 |
106 | sigset_t mask;
107 | sigemptyset(&mask);
108 | if (!return_on_signal)
109 | sigaddset(&mask, SIGWINCH);
110 |
111 | int err = pselect(
112 | tty->fdin + 1,
113 | &readfs,
114 | NULL,
115 | NULL,
116 | timeout < 0 ? NULL : &ts,
117 | return_on_signal ? NULL : &mask);
118 |
119 | if (err < 0) {
120 | if (errno == EINTR) {
121 | return 0;
122 | } else {
123 | perror("select");
124 | exit(EXIT_FAILURE);
125 | }
126 | } else {
127 | return FD_ISSET(tty->fdin, &readfs);
128 | }
129 | }
130 |
131 | static void tty_sgr(tty_t *tty, int code) {
132 | tty_printf(tty, "%c%c%im", 0x1b, '[', code);
133 | }
134 |
135 | void tty_setfg(tty_t *tty, int fg) {
136 | if (tty->fgcolor != fg) {
137 | tty_sgr(tty, 30 + fg);
138 | tty->fgcolor = fg;
139 | }
140 | }
141 |
142 | void tty_setinvert(tty_t *tty) {
143 | tty_sgr(tty, 7);
144 | }
145 |
146 | void tty_setunderline(tty_t *tty) {
147 | tty_sgr(tty, 4);
148 | }
149 |
150 | void tty_setnormal(tty_t *tty) {
151 | tty_sgr(tty, 0);
152 | tty->fgcolor = 9;
153 | }
154 |
155 | void tty_setnowrap(tty_t *tty) {
156 | tty_printf(tty, "%c%c?7l", 0x1b, '[');
157 | }
158 |
159 | void tty_setwrap(tty_t *tty) {
160 | tty_printf(tty, "%c%c?7h", 0x1b, '[');
161 | }
162 |
163 | void tty_newline(tty_t *tty) {
164 | tty_printf(tty, "%c%cK\n", 0x1b, '[');
165 | }
166 |
167 | void tty_clearline(tty_t *tty) {
168 | tty_printf(tty, "%c%cK", 0x1b, '[');
169 | }
170 |
171 | void tty_setcol(tty_t *tty, int col) {
172 | tty_printf(tty, "%c%c%iG", 0x1b, '[', col + 1);
173 | }
174 |
175 | void tty_moveup(tty_t *tty, int i) {
176 | tty_printf(tty, "%c%c%iA", 0x1b, '[', i);
177 | }
178 |
179 | void tty_printf(tty_t *tty, const char *fmt, ...) {
180 | va_list args;
181 | va_start(args, fmt);
182 | vfprintf(tty->fout, fmt, args);
183 | va_end(args);
184 | }
185 |
186 | void tty_putc(tty_t *tty, char c) {
187 | fputc(c, tty->fout);
188 | }
189 |
190 | void tty_flush(tty_t *tty) {
191 | fflush(tty->fout);
192 | }
193 |
194 | size_t tty_getwidth(tty_t *tty) {
195 | return tty->maxwidth;
196 | }
197 |
198 | size_t tty_getheight(tty_t *tty) {
199 | return tty->maxheight;
200 | }
201 |
--------------------------------------------------------------------------------
/src/tty.h:
--------------------------------------------------------------------------------
1 | #ifndef TTY_H
2 | #define TTY_H TTY_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #ifdef __cplusplus
9 | extern "C" {
10 | #endif
11 |
12 | typedef struct {
13 | int fdin;
14 | FILE *fout;
15 | struct termios original_termios;
16 | int fgcolor;
17 | size_t maxwidth;
18 | size_t maxheight;
19 | } tty_t;
20 |
21 | void tty_reset(tty_t *tty);
22 | void tty_close(tty_t *tty);
23 | void tty_init(tty_t *tty, const char *tty_filename);
24 | void tty_getwinsz(tty_t *tty);
25 | char tty_getchar(tty_t *tty);
26 | int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal);
27 |
28 | void tty_setfg(tty_t *tty, int fg);
29 | void tty_setinvert(tty_t *tty);
30 | void tty_setunderline(tty_t *tty);
31 | void tty_setnormal(tty_t *tty);
32 | void tty_setnowrap(tty_t *tty);
33 | void tty_setwrap(tty_t *tty);
34 |
35 | #define TTY_COLOR_BLACK 0
36 | #define TTY_COLOR_RED 1
37 | #define TTY_COLOR_GREEN 2
38 | #define TTY_COLOR_YELLOW 3
39 | #define TTY_COLOR_BLUE 4
40 | #define TTY_COLOR_MAGENTA 5
41 | #define TTY_COLOR_CYAN 6
42 | #define TTY_COLOR_WHITE 7
43 | #define TTY_COLOR_NORMAL 9
44 |
45 | /* tty_newline
46 | * Move cursor to the beginning of the next line, clearing to the end of the
47 | * current line
48 | */
49 | void tty_newline(tty_t *tty);
50 |
51 | /* tty_clearline
52 | * Clear to the end of the current line without advancing the cursor.
53 | */
54 | void tty_clearline(tty_t *tty);
55 |
56 | void tty_moveup(tty_t *tty, int i);
57 | void tty_setcol(tty_t *tty, int col);
58 |
59 | void tty_printf(tty_t *tty, const char *fmt, ...);
60 | void tty_putc(tty_t *tty, char c);
61 | void tty_flush(tty_t *tty);
62 |
63 | size_t tty_getwidth(tty_t *tty);
64 | size_t tty_getheight(tty_t *tty);
65 |
66 | #ifdef __cplusplus
67 | }
68 | #endif
69 |
70 | #endif
71 |
--------------------------------------------------------------------------------
/src/tty_interface.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | #include "match.h"
7 | #include "tty_interface.h"
8 | #include "../config.h"
9 |
10 | static int isprint_unicode(char c) {
11 | return isprint(c) || c & (1 << 7);
12 | }
13 |
14 | static int is_boundary(char c) {
15 | return ~c & (1 << 7) || c & (1 << 6);
16 | }
17 |
18 | static void clear(tty_interface_t *state) {
19 | tty_t *tty = state->tty;
20 |
21 | tty_setcol(tty, 0);
22 | size_t line = 0;
23 | while (line++ < state->options->num_lines + (state->options->show_info ? 1 : 0)) {
24 | tty_newline(tty);
25 | }
26 | tty_clearline(tty);
27 | if (state->options->num_lines > 0) {
28 | tty_moveup(tty, line - 1);
29 | }
30 | tty_flush(tty);
31 | }
32 |
33 | static void draw_match(tty_interface_t *state, const char *choice, int selected) {
34 | tty_t *tty = state->tty;
35 | options_t *options = state->options;
36 | char *search = state->last_search;
37 |
38 | int n = strlen(search);
39 | size_t positions[MATCH_MAX_LEN];
40 | for (int i = 0; i < n + 1 && i < MATCH_MAX_LEN; i++)
41 | positions[i] = -1;
42 |
43 | score_t score = match_positions(search, choice, &positions[0]);
44 |
45 | if (options->show_scores) {
46 | if (score == SCORE_MIN) {
47 | tty_printf(tty, "( ) ");
48 | } else {
49 | tty_printf(tty, "(%5.2f) ", score);
50 | }
51 | }
52 |
53 | if (selected)
54 | #ifdef TTY_SELECTION_UNDERLINE
55 | tty_setunderline(tty);
56 | #else
57 | tty_setinvert(tty);
58 | #endif
59 |
60 | tty_setnowrap(tty);
61 | for (size_t i = 0, p = 0; choice[i] != '\0'; i++) {
62 | if (positions[p] == i) {
63 | tty_setfg(tty, TTY_COLOR_HIGHLIGHT);
64 | p++;
65 | } else {
66 | tty_setfg(tty, TTY_COLOR_NORMAL);
67 | }
68 | if (choice[i] == '\n') {
69 | tty_putc(tty, ' ');
70 | } else {
71 | tty_printf(tty, "%c", choice[i]);
72 | }
73 | }
74 | tty_setwrap(tty);
75 | tty_setnormal(tty);
76 | }
77 |
78 | static void draw(tty_interface_t *state) {
79 | tty_t *tty = state->tty;
80 | choices_t *choices = state->choices;
81 | options_t *options = state->options;
82 |
83 | unsigned int num_lines = options->num_lines;
84 | size_t start = 0;
85 | size_t current_selection = choices->selection;
86 | if (current_selection + options->scrolloff >= num_lines) {
87 | start = current_selection + options->scrolloff - num_lines + 1;
88 | size_t available = choices_available(choices);
89 | if (start + num_lines >= available && available > 0) {
90 | start = available - num_lines;
91 | }
92 | }
93 |
94 | tty_setcol(tty, 0);
95 | tty_printf(tty, "%s%s", options->prompt, state->search);
96 | tty_clearline(tty);
97 |
98 | if (options->show_info) {
99 | tty_printf(tty, "\n[%lu/%lu]", choices->available, choices->size);
100 | tty_clearline(tty);
101 | }
102 |
103 | for (size_t i = start; i < start + num_lines; i++) {
104 | tty_printf(tty, "\n");
105 | tty_clearline(tty);
106 | const char *choice = choices_get(choices, i);
107 | if (choice) {
108 | draw_match(state, choice, i == choices->selection);
109 | }
110 | }
111 |
112 | if (num_lines + options->show_info)
113 | tty_moveup(tty, num_lines + options->show_info);
114 |
115 | tty_setcol(tty, 0);
116 | fputs(options->prompt, tty->fout);
117 | for (size_t i = 0; i < state->cursor; i++)
118 | fputc(state->search[i], tty->fout);
119 | tty_flush(tty);
120 | }
121 |
122 | static void update_search(tty_interface_t *state) {
123 | choices_search(state->choices, state->search);
124 | strcpy(state->last_search, state->search);
125 | }
126 |
127 | static void update_state(tty_interface_t *state) {
128 | if (strcmp(state->last_search, state->search)) {
129 | update_search(state);
130 | draw(state);
131 | }
132 | }
133 |
134 | static void action_emit(tty_interface_t *state) {
135 | update_state(state);
136 |
137 | /* Reset the tty as close as possible to the previous state */
138 | clear(state);
139 |
140 | /* ttyout should be flushed before outputting on stdout */
141 | tty_close(state->tty);
142 |
143 | const char *selection = choices_get(state->choices, state->choices->selection);
144 | if (selection) {
145 | /* output the selected result */
146 | printf("%s\n", selection);
147 | } else {
148 | /* No match, output the query instead */
149 | printf("%s\n", state->search);
150 | }
151 |
152 | state->exit = EXIT_SUCCESS;
153 | }
154 |
155 | static void action_del_char(tty_interface_t *state) {
156 | size_t length = strlen(state->search);
157 | if (state->cursor == 0) {
158 | return;
159 | }
160 | size_t original_cursor = state->cursor;
161 |
162 | do {
163 | state->cursor--;
164 | } while (!is_boundary(state->search[state->cursor]) && state->cursor);
165 |
166 | memmove(&state->search[state->cursor], &state->search[original_cursor], length - original_cursor + 1);
167 | }
168 |
169 | static void action_del_word(tty_interface_t *state) {
170 | size_t original_cursor = state->cursor;
171 | size_t cursor = state->cursor;
172 |
173 | while (cursor && isspace(state->search[cursor - 1]))
174 | cursor--;
175 |
176 | while (cursor && !isspace(state->search[cursor - 1]))
177 | cursor--;
178 |
179 | memmove(&state->search[cursor], &state->search[original_cursor], strlen(state->search) - original_cursor + 1);
180 | state->cursor = cursor;
181 | }
182 |
183 | static void action_del_all(tty_interface_t *state) {
184 | memmove(state->search, &state->search[state->cursor], strlen(state->search) - state->cursor + 1);
185 | state->cursor = 0;
186 | }
187 |
188 | static void action_prev(tty_interface_t *state) {
189 | update_state(state);
190 | choices_prev(state->choices);
191 | }
192 |
193 | static void action_ignore(tty_interface_t *state) {
194 | (void)state;
195 | }
196 |
197 | static void action_next(tty_interface_t *state) {
198 | update_state(state);
199 | choices_next(state->choices);
200 | }
201 |
202 | static void action_left(tty_interface_t *state) {
203 | if (state->cursor > 0) {
204 | state->cursor--;
205 | while (!is_boundary(state->search[state->cursor]) && state->cursor)
206 | state->cursor--;
207 | }
208 | }
209 |
210 | static void action_right(tty_interface_t *state) {
211 | if (state->cursor < strlen(state->search)) {
212 | state->cursor++;
213 | while (!is_boundary(state->search[state->cursor]))
214 | state->cursor++;
215 | }
216 | }
217 |
218 | static void action_beginning(tty_interface_t *state) {
219 | state->cursor = 0;
220 | }
221 |
222 | static void action_end(tty_interface_t *state) {
223 | state->cursor = strlen(state->search);
224 | }
225 |
226 | static void action_pageup(tty_interface_t *state) {
227 | update_state(state);
228 | for (size_t i = 0; i < state->options->num_lines && state->choices->selection > 0; i++)
229 | choices_prev(state->choices);
230 | }
231 |
232 | static void action_pagedown(tty_interface_t *state) {
233 | update_state(state);
234 | for (size_t i = 0; i < state->options->num_lines && state->choices->selection < state->choices->available - 1; i++)
235 | choices_next(state->choices);
236 | }
237 |
238 | static void action_autocomplete(tty_interface_t *state) {
239 | update_state(state);
240 | const char *current_selection = choices_get(state->choices, state->choices->selection);
241 | if (current_selection) {
242 | strncpy(state->search, choices_get(state->choices, state->choices->selection), SEARCH_SIZE_MAX);
243 | state->cursor = strlen(state->search);
244 | }
245 | }
246 |
247 | static void action_exit(tty_interface_t *state) {
248 | clear(state);
249 | tty_close(state->tty);
250 |
251 | state->exit = EXIT_FAILURE;
252 | }
253 |
254 | static void append_search(tty_interface_t *state, char ch) {
255 | char *search = state->search;
256 | size_t search_size = strlen(search);
257 | if (search_size < SEARCH_SIZE_MAX) {
258 | memmove(&search[state->cursor+1], &search[state->cursor], search_size - state->cursor + 1);
259 | search[state->cursor] = ch;
260 |
261 | state->cursor++;
262 | }
263 | }
264 |
265 | void tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options) {
266 | state->tty = tty;
267 | state->choices = choices;
268 | state->options = options;
269 | state->ambiguous_key_pending = 0;
270 |
271 | strcpy(state->input, "");
272 | strcpy(state->search, "");
273 | strcpy(state->last_search, "");
274 |
275 | state->exit = -1;
276 |
277 | if (options->init_search)
278 | strncpy(state->search, options->init_search, SEARCH_SIZE_MAX);
279 |
280 | state->cursor = strlen(state->search);
281 |
282 | update_search(state);
283 | }
284 |
285 | typedef struct {
286 | const char *key;
287 | void (*action)(tty_interface_t *);
288 | } keybinding_t;
289 |
290 | #define KEY_CTRL(key) ((const char[]){((key) - ('@')), '\0'})
291 |
292 | static const keybinding_t keybindings[] = {{"\x1b", action_exit}, /* ESC */
293 | {"\x7f", action_del_char}, /* DEL */
294 |
295 | {KEY_CTRL('H'), action_del_char}, /* Backspace (C-H) */
296 | {KEY_CTRL('W'), action_del_word}, /* C-W */
297 | {KEY_CTRL('U'), action_del_all}, /* C-U */
298 | {KEY_CTRL('I'), action_autocomplete}, /* TAB (C-I ) */
299 | {KEY_CTRL('C'), action_exit}, /* C-C */
300 | {KEY_CTRL('D'), action_exit}, /* C-D */
301 | {KEY_CTRL('G'), action_exit}, /* C-G */
302 | {KEY_CTRL('M'), action_emit}, /* CR */
303 | {KEY_CTRL('P'), action_prev}, /* C-P */
304 | {KEY_CTRL('N'), action_next}, /* C-N */
305 | {KEY_CTRL('K'), action_prev}, /* C-K */
306 | {KEY_CTRL('J'), action_next}, /* C-J */
307 | {KEY_CTRL('A'), action_beginning}, /* C-A */
308 | {KEY_CTRL('E'), action_end}, /* C-E */
309 |
310 | {"\x1bOD", action_left}, /* LEFT */
311 | {"\x1b[D", action_left}, /* LEFT */
312 | {"\x1bOC", action_right}, /* RIGHT */
313 | {"\x1b[C", action_right}, /* RIGHT */
314 | {"\x1b[1~", action_beginning}, /* HOME */
315 | {"\x1b[H", action_beginning}, /* HOME */
316 | {"\x1b[4~", action_end}, /* END */
317 | {"\x1b[F", action_end}, /* END */
318 | {"\x1b[A", action_prev}, /* UP */
319 | {"\x1bOA", action_prev}, /* UP */
320 | {"\x1b[B", action_next}, /* DOWN */
321 | {"\x1bOB", action_next}, /* DOWN */
322 | {"\x1b[5~", action_pageup},
323 | {"\x1b[6~", action_pagedown},
324 | {"\x1b[200~", action_ignore},
325 | {"\x1b[201~", action_ignore},
326 | {NULL, NULL}};
327 |
328 | #undef KEY_CTRL
329 |
330 | static void handle_input(tty_interface_t *state, const char *s, int handle_ambiguous_key) {
331 | state->ambiguous_key_pending = 0;
332 |
333 | char *input = state->input;
334 | strcat(state->input, s);
335 |
336 | /* Figure out if we have completed a keybinding and whether we're in the
337 | * middle of one (both can happen, because of Esc). */
338 | int found_keybinding = -1;
339 | int in_middle = 0;
340 | for (int i = 0; keybindings[i].key; i++) {
341 | if (!strcmp(input, keybindings[i].key))
342 | found_keybinding = i;
343 | else if (!strncmp(input, keybindings[i].key, strlen(state->input)))
344 | in_middle = 1;
345 | }
346 |
347 | /* If we have an unambiguous keybinding, run it. */
348 | if (found_keybinding != -1 && (!in_middle || handle_ambiguous_key)) {
349 | keybindings[found_keybinding].action(state);
350 | strcpy(input, "");
351 | return;
352 | }
353 |
354 | /* We could have a complete keybinding, or could be in the middle of one.
355 | * We'll need to wait a few milliseconds to find out. */
356 | if (found_keybinding != -1 && in_middle) {
357 | state->ambiguous_key_pending = 1;
358 | return;
359 | }
360 |
361 | /* Wait for more if we are in the middle of a keybinding */
362 | if (in_middle)
363 | return;
364 |
365 | /* No matching keybinding, add to search */
366 | for (int i = 0; input[i]; i++)
367 | if (isprint_unicode(input[i]))
368 | append_search(state, input[i]);
369 |
370 | /* We have processed the input, so clear it */
371 | strcpy(input, "");
372 | }
373 |
374 | int tty_interface_run(tty_interface_t *state) {
375 | draw(state);
376 |
377 | for (;;) {
378 | do {
379 | while(!tty_input_ready(state->tty, -1, 1)) {
380 | /* We received a signal (probably WINCH) */
381 | draw(state);
382 | }
383 |
384 | char s[2] = {tty_getchar(state->tty), '\0'};
385 | handle_input(state, s, 0);
386 |
387 | if (state->exit >= 0)
388 | return state->exit;
389 |
390 | draw(state);
391 | } while (tty_input_ready(state->tty, state->ambiguous_key_pending ? KEYTIMEOUT : 0, 0));
392 |
393 | if (state->ambiguous_key_pending) {
394 | char s[1] = "";
395 | handle_input(state, s, 1);
396 |
397 | if (state->exit >= 0)
398 | return state->exit;
399 | }
400 |
401 | update_state(state);
402 | }
403 |
404 | return state->exit;
405 | }
406 |
--------------------------------------------------------------------------------
/src/tty_interface.h:
--------------------------------------------------------------------------------
1 | #ifndef TTY_INTERFACE_H
2 | #define TTY_INTERFACE_H TTY_INTERFACE_H
3 |
4 | #include "choices.h"
5 | #include "options.h"
6 | #include "tty.h"
7 |
8 | #ifdef __cplusplus
9 | extern "C" {
10 | #endif
11 |
12 | #define SEARCH_SIZE_MAX 4096
13 |
14 | typedef struct {
15 | tty_t *tty;
16 | choices_t *choices;
17 | options_t *options;
18 |
19 | char search[SEARCH_SIZE_MAX + 1];
20 | char last_search[SEARCH_SIZE_MAX + 1];
21 | size_t cursor;
22 |
23 | int ambiguous_key_pending;
24 | char input[32]; /* Pending input buffer */
25 |
26 | int exit;
27 | } tty_interface_t;
28 |
29 | void tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options);
30 | int tty_interface_run(tty_interface_t *state);
31 |
32 | #ifdef __cplusplus
33 | }
34 | #endif
35 |
36 | #endif
37 |
--------------------------------------------------------------------------------
/test/acceptance/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem 'ttytest'
3 | gem 'minitest'
4 |
--------------------------------------------------------------------------------
/test/acceptance/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | minitest (5.10.1)
5 | ttytest (0.4.0)
6 |
7 | PLATFORMS
8 | ruby
9 |
10 | DEPENDENCIES
11 | minitest
12 | ttytest
13 |
14 | BUNDLED WITH
15 | 1.13.7
16 |
--------------------------------------------------------------------------------
/test/acceptance/acceptance_test.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | require 'minitest'
3 | require 'minitest/autorun'
4 | require 'ttytest'
5 |
6 | class FzyTest < Minitest::Test
7 | FZY_PATH = File.expand_path('../../../fzy', __FILE__)
8 |
9 | LEFT = "\e[D"
10 | RIGHT = "\e[C"
11 |
12 | def test_empty_list
13 | @tty = interactive_fzy(input: %w[], before: "placeholder")
14 | @tty.assert_cursor_position(y: 1, x: 2)
15 | @tty.assert_matches <<~TTY
16 | placeholder
17 | >
18 | TTY
19 |
20 | @tty.send_keys('t')
21 | @tty.assert_cursor_position(y: 1, x: 3)
22 | @tty.assert_matches <<~TTY
23 | placeholder
24 | > t
25 | TTY
26 |
27 | @tty.send_keys('z')
28 | @tty.assert_cursor_position(y: 1, x: 4)
29 | @tty.assert_matches <<~TTY
30 | placeholder
31 | > tz
32 | TTY
33 |
34 | @tty.send_keys("\r")
35 | @tty.assert_cursor_position(y: 2, x: 0)
36 | @tty.assert_matches <<~TTY
37 | placeholder
38 | tz
39 | TTY
40 | end
41 |
42 | def test_one_item
43 | @tty = interactive_fzy(input: %w[test], before: "placeholder")
44 | @tty.assert_matches <<~TTY
45 | placeholder
46 | >
47 | test
48 | TTY
49 | @tty.assert_cursor_position(y: 1, x: 2)
50 |
51 | @tty.send_keys('t')
52 | @tty.assert_cursor_position(y: 1, x: 3)
53 | @tty.assert_matches <<~TTY
54 | placeholder
55 | > t
56 | test
57 | TTY
58 |
59 | @tty.send_keys('z')
60 | @tty.assert_cursor_position(y: 1, x: 4)
61 | @tty.assert_matches <<~TTY
62 | placeholder
63 | > tz
64 | TTY
65 |
66 | @tty.send_keys("\r")
67 | @tty.assert_cursor_position(y: 2, x: 0)
68 | @tty.assert_matches <<~TTY
69 | placeholder
70 | tz
71 | TTY
72 | end
73 |
74 | def test_two_items
75 | @tty = interactive_fzy(input: %w[test foo], before: "placeholder")
76 | @tty.assert_cursor_position(y: 1, x: 2)
77 | @tty.assert_matches <<~TTY
78 | placeholder
79 | >
80 | test
81 | foo
82 | TTY
83 |
84 | @tty.send_keys('t')
85 | @tty.assert_cursor_position(y: 1, x: 3)
86 | @tty.assert_matches <<~TTY
87 | placeholder
88 | > t
89 | test
90 | TTY
91 |
92 | @tty.send_keys('z')
93 | @tty.assert_cursor_position(y: 1, x: 4)
94 | @tty.assert_matches <<~TTY
95 | placeholder
96 | > tz
97 | TTY
98 |
99 | @tty.send_keys("\r")
100 | @tty.assert_matches <<~TTY
101 | placeholder
102 | tz
103 | TTY
104 | @tty.assert_cursor_position(y: 2, x: 0)
105 | end
106 |
107 | def ctrl(key)
108 | ((key.upcase.ord) - ('A'.ord) + 1).chr
109 | end
110 |
111 | def test_editing
112 | @tty = interactive_fzy(input: %w[test foo], before: "placeholder")
113 | @tty.assert_cursor_position(y: 1, x: 2)
114 | @tty.assert_matches <<~TTY
115 | placeholder
116 | >
117 | test
118 | foo
119 | TTY
120 |
121 | @tty.send_keys("foo bar baz")
122 | @tty.assert_cursor_position(y: 1, x: 13)
123 | @tty.assert_matches <<~TTY
124 | placeholder
125 | > foo bar baz
126 | TTY
127 |
128 | @tty.send_keys(ctrl('H'))
129 | @tty.assert_cursor_position(y: 1, x: 12)
130 | @tty.assert_matches <<~TTY
131 | placeholder
132 | > foo bar ba
133 | TTY
134 |
135 | @tty.send_keys(ctrl('W'))
136 | @tty.assert_cursor_position(y: 1, x: 10)
137 | @tty.assert_matches <<~TTY
138 | placeholder
139 | > foo bar
140 | TTY
141 |
142 | @tty.send_keys(ctrl('U'))
143 | @tty.assert_cursor_position(y: 1, x: 2)
144 | @tty.assert_matches <<~TTY
145 | placeholder
146 | >
147 | test
148 | foo
149 | TTY
150 | end
151 |
152 | def test_ctrl_d
153 | @tty = interactive_fzy(input: %w[foo bar])
154 | @tty.assert_matches ">\nfoo\nbar"
155 |
156 | @tty.send_keys('foo')
157 | @tty.assert_matches "> foo\nfoo"
158 |
159 | @tty.send_keys(ctrl('D'))
160 | @tty.assert_matches ''
161 | @tty.assert_cursor_position(y: 0, x: 0)
162 | end
163 |
164 | def test_ctrl_c
165 | @tty = interactive_fzy(input: %w[foo bar])
166 | @tty.assert_matches ">\nfoo\nbar"
167 |
168 | @tty.send_keys('foo')
169 | @tty.assert_matches "> foo\nfoo"
170 |
171 | @tty.send_keys(ctrl('C'))
172 | @tty.assert_matches ''
173 | @tty.assert_cursor_position(y: 0, x: 0)
174 | end
175 |
176 | def test_down_arrow
177 | @tty = interactive_fzy(input: %w[foo bar])
178 | @tty.assert_matches ">\nfoo\nbar"
179 | @tty.send_keys("\e[A\r")
180 | @tty.assert_matches "bar"
181 |
182 | @tty = interactive_fzy(input: %w[foo bar])
183 | @tty.assert_matches ">\nfoo\nbar"
184 | @tty.send_keys("\eOA\r")
185 | @tty.assert_matches "bar"
186 | end
187 |
188 | def test_up_arrow
189 | @tty = interactive_fzy(input: %w[foo bar])
190 | @tty.assert_matches ">\nfoo\nbar"
191 | @tty.send_keys("\e[A") # first down
192 | @tty.send_keys("\e[B\r") # and back up
193 | @tty.assert_matches "foo"
194 |
195 | @tty = interactive_fzy(input: %w[foo bar])
196 | @tty.assert_matches ">\nfoo\nbar"
197 | @tty.send_keys("\eOA") # first down
198 | @tty.send_keys("\e[B\r") # and back up
199 | @tty.assert_matches "foo"
200 | end
201 |
202 | def test_lines
203 | input10 = (1..10).map(&:to_s)
204 | input20 = (1..20).map(&:to_s)
205 |
206 | @tty = interactive_fzy(input: input10)
207 | @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10"
208 |
209 | @tty = interactive_fzy(input: input20)
210 | @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10"
211 |
212 | @tty = interactive_fzy(input: input10, args: "-l 5")
213 | @tty.assert_matches ">\n1\n2\n3\n4\n5"
214 |
215 | @tty = interactive_fzy(input: input10, args: "--lines=5")
216 | @tty.assert_matches ">\n1\n2\n3\n4\n5"
217 | end
218 |
219 | def test_prompt
220 | @tty = interactive_fzy
221 | @tty.send_keys("foo")
222 | @tty.assert_matches '> foo'
223 |
224 | @tty = interactive_fzy(args: "-p 'C:\\'")
225 | @tty.send_keys("foo")
226 | @tty.assert_matches 'C:\foo'
227 |
228 | @tty = interactive_fzy(args: "--prompt=\"foo bar \"")
229 | @tty.send_keys("baz")
230 | @tty.assert_matches "foo bar baz"
231 | end
232 |
233 | def test_show_scores
234 | expected_score = '( inf)'
235 | @tty = interactive_fzy(input: %w[foo bar], args: "-s")
236 | @tty.send_keys('foo')
237 | @tty.assert_matches "> foo\n#{expected_score} foo"
238 |
239 | @tty = interactive_fzy(input: %w[foo bar], args: "--show-scores")
240 | @tty.send_keys('foo')
241 | @tty.assert_matches "> foo\n#{expected_score} foo"
242 |
243 | expected_score = '( 0.89)'
244 | @tty = interactive_fzy(input: %w[foo bar], args: "-s")
245 | @tty.send_keys('f')
246 | @tty.assert_matches "> f\n#{expected_score} foo"
247 | end
248 |
249 | def test_large_input
250 | @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -l 3})
251 | @tty.send_keys('34')
252 | @tty.assert_matches "> 34\n34\n340\n341"
253 |
254 | @tty.send_keys('5')
255 | @tty.assert_matches "> 345\n345\n3450\n3451"
256 |
257 | @tty.send_keys('z')
258 | @tty.assert_matches "> 345z"
259 | end
260 |
261 | def test_worker_count
262 | @tty = interactive_fzy(input: %w[foo bar], args: "-j1")
263 | @tty.send_keys('foo')
264 | @tty.assert_matches "> foo\nfoo"
265 |
266 | @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -j1 -l3})
267 | @tty.send_keys('34')
268 | @tty.assert_matches "> 34\n34\n340\n341"
269 |
270 | @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -j200 -l3})
271 | @tty.send_keys('34')
272 | @tty.assert_matches "> 34\n34\n340\n341"
273 | end
274 |
275 | def test_initial_query
276 | @tty = interactive_fzy(input: %w[foo bar], args: "-q fo")
277 | @tty.assert_matches "> fo\nfoo"
278 | @tty.send_keys("o")
279 | @tty.assert_matches "> foo\nfoo"
280 | @tty.send_keys("o")
281 | @tty.assert_matches "> fooo"
282 |
283 | @tty = interactive_fzy(input: %w[foo bar], args: "-q asdf")
284 | @tty.assert_matches "> asdf"
285 | end
286 |
287 | def test_non_interactive
288 | @tty = interactive_fzy(input: %w[foo bar], args: "-e foo", before: "before", after: "after")
289 | @tty.assert_matches "before\nfoo\nafter"
290 | end
291 |
292 | def test_moving_text_cursor
293 | @tty = interactive_fzy(input: %w[foo bar])
294 | @tty.send_keys("br")
295 | @tty.assert_matches "> br\nbar"
296 | @tty.assert_cursor_position(y: 0, x: 4)
297 |
298 | @tty.send_keys(LEFT)
299 | @tty.assert_cursor_position(y: 0, x: 3)
300 | @tty.assert_matches "> br\nbar"
301 | @tty.send_keys("a")
302 | @tty.assert_cursor_position(y: 0, x: 4)
303 | @tty.assert_matches "> bar\nbar"
304 |
305 | @tty.send_keys(ctrl("A")) # Ctrl-A
306 | @tty.assert_cursor_position(y: 0, x: 2)
307 | @tty.assert_matches "> bar\nbar"
308 | @tty.send_keys("foo")
309 | @tty.assert_cursor_position(y: 0, x: 5)
310 | @tty.assert_matches "> foobar"
311 |
312 | @tty.send_keys(ctrl("E")) # Ctrl-E
313 | @tty.assert_cursor_position(y: 0, x: 8)
314 | @tty.assert_matches "> foobar"
315 | @tty.send_keys("baz") # Ctrl-E
316 | @tty.assert_cursor_position(y: 0, x: 11)
317 | @tty.assert_matches "> foobarbaz"
318 | end
319 |
320 | # More info;
321 | # https://github.com/jhawthorn/fzy/issues/42
322 | # https://cirw.in/blog/bracketed-paste
323 | def test_bracketed_paste_characters
324 | @tty = interactive_fzy(input: %w[foo bar])
325 | @tty.assert_matches ">\nfoo\nbar"
326 | @tty.send_keys("\e[200~foo\e[201~")
327 | @tty.assert_matches "> foo\nfoo"
328 | end
329 |
330 | # https://github.com/jhawthorn/fzy/issues/81
331 | def test_slow_stdin_fast_user
332 | @tty = TTYtest.new_terminal(%{(sleep 0.5; echo aa; echo bc; echo bd) | #{FZY_PATH}})
333 |
334 | # Before input has all come in, but wait for fzy to at least start
335 | sleep 0.1
336 |
337 | @tty.send_keys("b\r")
338 | @tty.assert_matches "bc"
339 | end
340 |
341 | def test_unicode
342 | @tty = interactive_fzy(input: %w[English Français 日本語])
343 | @tty.assert_matches <<~TTY
344 | >
345 | English
346 | Français
347 | 日本語
348 | TTY
349 | @tty.assert_cursor_position(y: 0, x: 2)
350 |
351 | @tty.send_keys("ç")
352 | @tty.assert_matches <<~TTY
353 | > ç
354 | Français
355 | TTY
356 | @tty.assert_cursor_position(y: 0, x: 3)
357 |
358 | @tty.send_keys("\r")
359 | @tty.assert_matches "Français"
360 | end
361 |
362 | def test_unicode_backspace
363 | @tty = interactive_fzy
364 | @tty.send_keys "Français"
365 | @tty.assert_matches "> Français"
366 | @tty.assert_cursor_position(y: 0, x: 10)
367 |
368 | @tty.send_keys(ctrl('H') * 3)
369 | @tty.assert_matches "> Franç"
370 | @tty.assert_cursor_position(y: 0, x: 7)
371 |
372 | @tty.send_keys(ctrl('H'))
373 | @tty.assert_matches "> Fran"
374 | @tty.assert_cursor_position(y: 0, x: 6)
375 |
376 | @tty.send_keys('ce')
377 | @tty.assert_matches "> France"
378 |
379 | @tty = interactive_fzy
380 | @tty.send_keys "日本語"
381 | @tty.assert_matches "> 日本語"
382 | @tty.send_keys(ctrl('H'))
383 | @tty.assert_matches "> 日本"
384 | @tty.send_keys(ctrl('H'))
385 | @tty.assert_matches "> 日"
386 | @tty.send_keys(ctrl('H'))
387 | @tty.assert_matches "> "
388 | @tty.assert_cursor_position(y: 0, x: 2)
389 | end
390 |
391 | def test_unicode_delete_word
392 | @tty = interactive_fzy
393 | @tty.send_keys "Je parle Français"
394 | @tty.assert_matches "> Je parle Français"
395 | @tty.assert_cursor_position(y: 0, x: 19)
396 |
397 | @tty.send_keys(ctrl('W'))
398 | @tty.assert_matches "> Je parle"
399 | @tty.assert_cursor_position(y: 0, x: 11)
400 |
401 | @tty = interactive_fzy
402 | @tty.send_keys "日本語"
403 | @tty.assert_matches "> 日本語"
404 | @tty.send_keys(ctrl('W'))
405 | @tty.assert_matches "> "
406 | @tty.assert_cursor_position(y: 0, x: 2)
407 | end
408 |
409 | def test_unicode_cursor_movement
410 | @tty = interactive_fzy
411 | @tty.send_keys "Français"
412 | @tty.assert_cursor_position(y: 0, x: 10)
413 |
414 | @tty.send_keys(LEFT*5)
415 | @tty.assert_cursor_position(y: 0, x: 5)
416 |
417 | @tty.send_keys(RIGHT*3)
418 | @tty.assert_cursor_position(y: 0, x: 8)
419 |
420 | @tty = interactive_fzy
421 | @tty.send_keys "日本語"
422 | @tty.assert_matches "> 日本語"
423 | @tty.assert_cursor_position(y: 0, x: 8)
424 | @tty.send_keys(LEFT)
425 | @tty.assert_cursor_position(y: 0, x: 6)
426 | @tty.send_keys(LEFT)
427 | @tty.assert_cursor_position(y: 0, x: 4)
428 | @tty.send_keys(LEFT)
429 | @tty.assert_cursor_position(y: 0, x: 2)
430 | @tty.send_keys(LEFT)
431 | @tty.assert_cursor_position(y: 0, x: 2)
432 | @tty.send_keys(RIGHT*3)
433 | @tty.assert_cursor_position(y: 0, x: 8)
434 | @tty.send_keys(RIGHT)
435 | @tty.assert_cursor_position(y: 0, x: 8)
436 | end
437 |
438 | def test_long_strings
439 | ascii = "LongStringOfText" * 6
440 | unicode = "LongStringOfText" * 3
441 |
442 | @tty = interactive_fzy(input: [ascii, unicode])
443 | @tty.assert_matches <<~TTY
444 | >
445 | LongStringOfTextLongStringOfTextLongStringOfTextLongStringOfTextLongStringOfText
446 | LongStringOfTextLongStringOfTextLongStri
447 | TTY
448 | end
449 |
450 | def test_show_info
451 | @tty = interactive_fzy(input: %w[foo bar baz], args: "-i")
452 | @tty.assert_matches ">\n[3/3]\nfoo\nbar\nbaz"
453 | @tty.send_keys("ba")
454 | @tty.assert_matches "> ba\n[2/3]\nbar\nbaz"
455 | @tty.send_keys("q")
456 | @tty.assert_matches "> baq\n[0/3]"
457 | end
458 |
459 | def test_help
460 | @tty = TTYtest.new_terminal(%{#{FZY_PATH} --help})
461 | @tty.assert_matches < ')
465 | -q, --query=QUERY Use QUERY as the initial search string
466 | -e, --show-matches=QUERY Output the sorted matches of QUERY
467 | -t, --tty=TTY Specify file to use as TTY device (default /dev/tty)
468 | -s, --show-scores Show the scores of each match
469 | -0, --read-null Read input delimited by ASCII NUL characters
470 | -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)
471 | -i, --show-info Show selection info line
472 | -h, --help Display this help and exit
473 | -v, --version Output version information and exit
474 | TTY
475 | end
476 |
477 | private
478 |
479 | def interactive_fzy(input: [], before: nil, after: nil, args: "")
480 | cmd = []
481 | cmd << %{echo "#{before}"} if before
482 | cmd << %{printf "#{input.join("\\n")}" | #{FZY_PATH} #{args}}
483 | cmd << %{echo "#{after}"} if after
484 | cmd = cmd.join("; ")
485 | TTYtest.new_terminal(cmd)
486 | end
487 | end
488 |
--------------------------------------------------------------------------------
/test/fzytest.c:
--------------------------------------------------------------------------------
1 | #include "greatest/greatest.h"
2 |
3 | SUITE(match_suite);
4 | SUITE(choices_suite);
5 | SUITE(properties_suite);
6 |
7 | GREATEST_MAIN_DEFS();
8 |
9 | int main(int argc, char *argv[]) {
10 | GREATEST_MAIN_BEGIN();
11 |
12 | RUN_SUITE(match_suite);
13 | RUN_SUITE(choices_suite);
14 | RUN_SUITE(properties_suite);
15 |
16 | GREATEST_MAIN_END();
17 | }
18 |
--------------------------------------------------------------------------------
/test/test_choices.c:
--------------------------------------------------------------------------------
1 | #define _GNU_SOURCE
2 | #include
3 | #include
4 |
5 | #include "../config.h"
6 | #include "options.h"
7 | #include "choices.h"
8 |
9 | #include "greatest/greatest.h"
10 |
11 | #define ASSERT_SIZE_T_EQ(a,b) ASSERT_EQ_FMT((size_t)(a), (b), "%zu")
12 |
13 | static options_t default_options;
14 | static choices_t choices;
15 |
16 | static void setup(void *udata) {
17 | (void)udata;
18 |
19 | options_init(&default_options);
20 | choices_init(&choices, &default_options);
21 | }
22 |
23 | static void teardown(void *udata) {
24 | (void)udata;
25 | choices_destroy(&choices);
26 | }
27 |
28 | TEST test_choices_empty() {
29 | ASSERT_SIZE_T_EQ(0, choices.size);
30 | ASSERT_SIZE_T_EQ(0, choices.available);
31 | ASSERT_SIZE_T_EQ(0, choices.selection);
32 |
33 | choices_prev(&choices);
34 | ASSERT_SIZE_T_EQ(0, choices.selection);
35 |
36 | choices_next(&choices);
37 | ASSERT_SIZE_T_EQ(0, choices.selection);
38 |
39 | PASS();
40 | }
41 |
42 | TEST test_choices_1() {
43 | choices_add(&choices, "tags");
44 |
45 | choices_search(&choices, "");
46 | ASSERT_SIZE_T_EQ(1, choices.available);
47 | ASSERT_SIZE_T_EQ(0, choices.selection);
48 |
49 | choices_search(&choices, "t");
50 | ASSERT_SIZE_T_EQ(1, choices.available);
51 | ASSERT_SIZE_T_EQ(0, choices.selection);
52 |
53 | choices_prev(&choices);
54 | ASSERT_SIZE_T_EQ(0, choices.selection);
55 |
56 | choices_next(&choices);
57 | ASSERT_SIZE_T_EQ(0, choices.selection);
58 |
59 | ASSERT(!strcmp(choices_get(&choices, 0), "tags"));
60 | ASSERT_EQ(NULL, choices_get(&choices, 1));
61 |
62 | PASS();
63 | }
64 |
65 | TEST test_choices_2() {
66 | choices_add(&choices, "tags");
67 | choices_add(&choices, "test");
68 |
69 | /* Empty search */
70 | choices_search(&choices, "");
71 | ASSERT_SIZE_T_EQ(0, choices.selection);
72 | ASSERT_SIZE_T_EQ(2, choices.available);
73 |
74 | choices_next(&choices);
75 | ASSERT_SIZE_T_EQ(1, choices.selection);
76 | choices_next(&choices);
77 | ASSERT_SIZE_T_EQ(0, choices.selection);
78 |
79 | choices_prev(&choices);
80 | ASSERT_SIZE_T_EQ(1, choices.selection);
81 | choices_prev(&choices);
82 | ASSERT_SIZE_T_EQ(0, choices.selection);
83 |
84 | /* Filtered search */
85 | choices_search(&choices, "te");
86 | ASSERT_SIZE_T_EQ(1, choices.available);
87 | ASSERT_SIZE_T_EQ(0, choices.selection);
88 | ASSERT_STR_EQ("test", choices_get(&choices, 0));
89 |
90 | choices_next(&choices);
91 | ASSERT_SIZE_T_EQ(0, choices.selection);
92 |
93 | choices_prev(&choices);
94 | ASSERT_SIZE_T_EQ(0, choices.selection);
95 |
96 | /* No results */
97 | choices_search(&choices, "foobar");
98 | ASSERT_SIZE_T_EQ(0, choices.available);
99 | ASSERT_SIZE_T_EQ(0, choices.selection);
100 |
101 | /* Different order due to scoring */
102 | choices_search(&choices, "ts");
103 | ASSERT_SIZE_T_EQ(2, choices.available);
104 | ASSERT_SIZE_T_EQ(0, choices.selection);
105 | ASSERT_STR_EQ("test", choices_get(&choices, 0));
106 | ASSERT_STR_EQ("tags", choices_get(&choices, 1));
107 |
108 | PASS();
109 | }
110 |
111 | TEST test_choices_without_search() {
112 | /* Before a search is run, it should return no results */
113 |
114 | ASSERT_SIZE_T_EQ(0, choices.available);
115 | ASSERT_SIZE_T_EQ(0, choices.selection);
116 | ASSERT_SIZE_T_EQ(0, choices.size);
117 | ASSERT_EQ(NULL, choices_get(&choices, 0));
118 |
119 | choices_add(&choices, "test");
120 |
121 | ASSERT_SIZE_T_EQ(0, choices.available);
122 | ASSERT_SIZE_T_EQ(0, choices.selection);
123 | ASSERT_SIZE_T_EQ(1, choices.size);
124 | ASSERT_EQ(NULL, choices_get(&choices, 0));
125 |
126 | PASS();
127 | }
128 |
129 | /* Regression test for segfault */
130 | TEST test_choices_unicode() {
131 | choices_add(&choices, "Edmund Husserl - Méditations cartésiennes - Introduction a la phénoménologie.pdf");
132 | choices_search(&choices, "e");
133 |
134 | PASS();
135 | }
136 |
137 | TEST test_choices_large_input() {
138 | const int N = 100000;
139 | char *strings[100000];
140 |
141 | for(int i = 0; i < N; i++) {
142 | asprintf(&strings[i], "%i", i);
143 | choices_add(&choices, strings[i]);
144 | }
145 |
146 | choices_search(&choices, "12");
147 |
148 | /* Must match `seq 0 99999 | grep '.*1.*2.*' | wc -l` */
149 | ASSERT_SIZE_T_EQ(8146, choices.available);
150 |
151 | ASSERT_STR_EQ("12", choices_get(&choices, 0));
152 |
153 | for(int i = 0; i < N; i++) {
154 | free(strings[i]);
155 | }
156 |
157 | PASS();
158 | }
159 |
160 | SUITE(choices_suite) {
161 | SET_SETUP(setup, NULL);
162 | SET_TEARDOWN(teardown, NULL);
163 |
164 | RUN_TEST(test_choices_empty);
165 | RUN_TEST(test_choices_1);
166 | RUN_TEST(test_choices_2);
167 | RUN_TEST(test_choices_without_search);
168 | RUN_TEST(test_choices_unicode);
169 | RUN_TEST(test_choices_large_input);
170 | }
171 |
--------------------------------------------------------------------------------
/test/test_match.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "../config.h"
4 | #include "match.h"
5 |
6 | #include "greatest/greatest.h"
7 |
8 | #define SCORE_TOLERANCE 0.000001
9 | #define ASSERT_SCORE_EQ(a,b) ASSERT_IN_RANGE((a), (b), SCORE_TOLERANCE)
10 | #define ASSERT_SIZE_T_EQ(a,b) ASSERT_EQ_FMT((size_t)(a), (b), "%zu")
11 |
12 | /* has_match(char *needle, char *haystack) */
13 | TEST exact_match_should_return_true() {
14 | ASSERT(has_match("a", "a"));
15 | PASS();
16 | }
17 |
18 | TEST partial_match_should_return_true() {
19 | ASSERT(has_match("a", "ab"));
20 | ASSERT(has_match("a", "ba"));
21 | PASS();
22 | }
23 |
24 | TEST match_with_delimiters_in_between() {
25 | ASSERT(has_match("abc", "a|b|c"));
26 | PASS();
27 | }
28 |
29 | TEST non_match_should_return_false() {
30 | ASSERT(!has_match("a", ""));
31 | ASSERT(!has_match("a", "b"));
32 | ASSERT(!has_match("ass", "tags"));
33 | PASS();
34 | }
35 |
36 | TEST empty_query_should_always_match() {
37 | /* match when query is empty */
38 | ASSERT(has_match("", ""));
39 | ASSERT(has_match("", "a"));
40 | PASS();
41 | }
42 |
43 | /* match(char *needle, char *haystack) */
44 |
45 | TEST should_prefer_starts_of_words() {
46 | /* App/Models/Order is better than App/MOdels/zRder */
47 | ASSERT(match("amor", "app/models/order") > match("amor", "app/models/zrder"));
48 | PASS();
49 | }
50 |
51 | TEST should_prefer_consecutive_letters() {
52 | /* App/MOdels/foo is better than App/M/fOo */
53 | ASSERT(match("amo", "app/m/foo") < match("amo", "app/models/foo"));
54 | PASS();
55 | }
56 |
57 | TEST should_prefer_contiguous_over_letter_following_period() {
58 | /* GEMFIle.Lock < GEMFILe */
59 | ASSERT(match("gemfil", "Gemfile.lock") < match("gemfil", "Gemfile"));
60 | PASS();
61 | }
62 |
63 | TEST should_prefer_shorter_matches() {
64 | ASSERT(match("abce", "abcdef") > match("abce", "abc de"));
65 | ASSERT(match("abc", " a b c ") > match("abc", " a b c "));
66 | ASSERT(match("abc", " a b c ") > match("abc", " a b c "));
67 | PASS();
68 | }
69 |
70 | TEST should_prefer_shorter_candidates() {
71 | ASSERT(match("test", "tests") > match("test", "testing"));
72 | PASS();
73 | }
74 |
75 | TEST should_prefer_start_of_candidate() {
76 | /* Scores first letter highly */
77 | ASSERT(match("test", "testing") > match("test", "/testing"));
78 | PASS();
79 | }
80 |
81 | TEST score_exact_match() {
82 | /* Exact match is SCORE_MAX */
83 | ASSERT_SCORE_EQ(SCORE_MAX, match("abc", "abc"));
84 | ASSERT_SCORE_EQ(SCORE_MAX, match("aBc", "abC"));
85 | PASS();
86 | }
87 |
88 | TEST score_empty_query() {
89 | /* Empty query always results in SCORE_MIN */
90 | ASSERT_SCORE_EQ(SCORE_MIN, match("", ""));
91 | ASSERT_SCORE_EQ(SCORE_MIN, match("", "a"));
92 | ASSERT_SCORE_EQ(SCORE_MIN, match("", "bb"));
93 | PASS();
94 | }
95 |
96 | TEST score_gaps() {
97 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING, match("a", "*a"));
98 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2, match("a", "*ba"));
99 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_GAP_TRAILING, match("a", "**a*"));
100 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_GAP_TRAILING*2, match("a", "**a**"));
101 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CONSECUTIVE + SCORE_GAP_TRAILING*2, match("aa", "**aa**"));
102 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_GAP_TRAILING + SCORE_GAP_TRAILING, match("aa", "**a*a**"));
103 | PASS();
104 | }
105 |
106 | TEST score_consecutive() {
107 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CONSECUTIVE, match("aa", "*aa"));
108 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CONSECUTIVE*2, match("aaa", "*aaa"));
109 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_MATCH_CONSECUTIVE, match("aaa", "*a*aa"));
110 | PASS();
111 | }
112 |
113 | TEST score_slash() {
114 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_SLASH, match("a", "/a"));
115 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_SLASH, match("a", "*/a"));
116 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_SLASH + SCORE_MATCH_CONSECUTIVE, match("aa", "a/aa"));
117 | PASS();
118 | }
119 |
120 | TEST score_capital() {
121 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CAPITAL, match("a", "bA"));
122 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CAPITAL, match("a", "baA"));
123 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CAPITAL + SCORE_MATCH_CONSECUTIVE, match("aa", "baAa"));
124 | PASS();
125 | }
126 |
127 | TEST score_dot() {
128 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_DOT, match("a", ".a"));
129 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*3 + SCORE_MATCH_DOT, match("a", "*a.a"));
130 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_MATCH_DOT, match("a", "*a.a"));
131 | PASS();
132 | }
133 |
134 | TEST score_long_string() {
135 | char string[4096];
136 | memset(string, 'a', sizeof(string) - 1);
137 | string[sizeof(string) - 1] = '\0';
138 |
139 | ASSERT_SCORE_EQ(SCORE_MIN, match("aa", string));
140 | ASSERT_SCORE_EQ(SCORE_MIN, match(string, "aa"));
141 | ASSERT_SCORE_EQ(SCORE_MIN, match(string, string));
142 |
143 | PASS();
144 | }
145 |
146 | TEST positions_consecutive() {
147 | size_t positions[3];
148 | match_positions("amo", "app/models/foo", positions);
149 | ASSERT_SIZE_T_EQ(0, positions[0]);
150 | ASSERT_SIZE_T_EQ(4, positions[1]);
151 | ASSERT_SIZE_T_EQ(5, positions[2]);
152 |
153 | PASS();
154 | }
155 |
156 | TEST positions_start_of_word() {
157 | /*
158 | * We should prefer matching the 'o' in order, since it's the beginning
159 | * of a word.
160 | */
161 | size_t positions[4];
162 | match_positions("amor", "app/models/order", positions);
163 | ASSERT_SIZE_T_EQ(0, positions[0]);
164 | ASSERT_SIZE_T_EQ(4, positions[1]);
165 | ASSERT_SIZE_T_EQ(11, positions[2]);
166 | ASSERT_SIZE_T_EQ(12, positions[3]);
167 |
168 | PASS();
169 | }
170 |
171 | TEST positions_no_bonuses() {
172 | size_t positions[2];
173 | match_positions("as", "tags", positions);
174 | ASSERT_SIZE_T_EQ(1, positions[0]);
175 | ASSERT_SIZE_T_EQ(3, positions[1]);
176 |
177 | match_positions("as", "examples.txt", positions);
178 | ASSERT_SIZE_T_EQ(2, positions[0]);
179 | ASSERT_SIZE_T_EQ(7, positions[1]);
180 |
181 | PASS();
182 | }
183 |
184 | TEST positions_multiple_candidates_start_of_words() {
185 | size_t positions[3];
186 | match_positions("abc", "a/a/b/c/c", positions);
187 | ASSERT_SIZE_T_EQ(2, positions[0]);
188 | ASSERT_SIZE_T_EQ(4, positions[1]);
189 | ASSERT_SIZE_T_EQ(6, positions[2]);
190 |
191 | PASS();
192 | }
193 |
194 | TEST positions_exact_match() {
195 | size_t positions[3];
196 | match_positions("foo", "foo", positions);
197 | ASSERT_SIZE_T_EQ(0, positions[0]);
198 | ASSERT_SIZE_T_EQ(1, positions[1]);
199 | ASSERT_SIZE_T_EQ(2, positions[2]);
200 |
201 | PASS();
202 | }
203 |
204 | SUITE(match_suite) {
205 | RUN_TEST(exact_match_should_return_true);
206 | RUN_TEST(partial_match_should_return_true);
207 | RUN_TEST(empty_query_should_always_match);
208 | RUN_TEST(non_match_should_return_false);
209 | RUN_TEST(match_with_delimiters_in_between);
210 |
211 | RUN_TEST(should_prefer_starts_of_words);
212 | RUN_TEST(should_prefer_consecutive_letters);
213 | RUN_TEST(should_prefer_contiguous_over_letter_following_period);
214 | RUN_TEST(should_prefer_shorter_matches);
215 | RUN_TEST(should_prefer_shorter_candidates);
216 | RUN_TEST(should_prefer_start_of_candidate);
217 |
218 | RUN_TEST(score_exact_match);
219 | RUN_TEST(score_empty_query);
220 | RUN_TEST(score_gaps);
221 | RUN_TEST(score_consecutive);
222 | RUN_TEST(score_slash);
223 | RUN_TEST(score_capital);
224 | RUN_TEST(score_dot);
225 | RUN_TEST(score_long_string);
226 |
227 | RUN_TEST(positions_consecutive);
228 | RUN_TEST(positions_start_of_word);
229 | RUN_TEST(positions_no_bonuses);
230 | RUN_TEST(positions_multiple_candidates_start_of_words);
231 | RUN_TEST(positions_exact_match);
232 | }
233 |
--------------------------------------------------------------------------------
/test/test_properties.c:
--------------------------------------------------------------------------------
1 | #define _DEFAULT_SOURCE
2 | #include
3 |
4 | #include "greatest/greatest.h"
5 | #include "theft/theft.h"
6 |
7 | #include "match.h"
8 |
9 | static void *string_alloc_cb(struct theft *t, theft_hash seed, void *env) {
10 | (void)env;
11 | int limit = 128;
12 |
13 | size_t sz = (size_t)(seed % limit) + 1;
14 | char *str = malloc(sz + 1);
15 | if (str == NULL) {
16 | return THEFT_ERROR;
17 | }
18 |
19 | for (size_t i = 0; i < sz; i += sizeof(theft_hash)) {
20 | theft_hash s = theft_random(t);
21 | for (uint8_t b = 0; b < sizeof(theft_hash); b++) {
22 | if (i + b >= sz) {
23 | break;
24 | }
25 | str[i + b] = (uint8_t)(s >> (8 * b)) & 0xff;
26 | }
27 | }
28 | str[sz] = 0;
29 |
30 | return str;
31 | }
32 |
33 | static void string_free_cb(void *instance, void *env) {
34 | free(instance);
35 | (void)env;
36 | }
37 |
38 | static void string_print_cb(FILE *f, void *instance, void *env) {
39 | char *str = (char *)instance;
40 | (void)env;
41 | size_t size = strlen(str);
42 | fprintf(f, "str[%zd]:\n ", size);
43 | uint8_t bytes = 0;
44 | for (size_t i = 0; i < size; i++) {
45 | fprintf(f, "%02x", str[i]);
46 | bytes++;
47 | if (bytes == 16) {
48 | fprintf(f, "\n ");
49 | bytes = 0;
50 | }
51 | }
52 | fprintf(f, "\n");
53 | }
54 |
55 | static uint64_t string_hash_cb(void *instance, void *env) {
56 | (void)env;
57 | char *str = (char *)instance;
58 | int size = strlen(str);
59 | return theft_hash_onepass((uint8_t *)str, size);
60 | }
61 |
62 | static void *string_shrink_cb(void *instance, uint32_t tactic, void *env) {
63 | (void)env;
64 | char *str = (char *)instance;
65 | int n = strlen(str);
66 |
67 | if (tactic == 0) { /* first half */
68 | return strndup(str, n / 2);
69 | } else if (tactic == 1) { /* second half */
70 | return strndup(str + (n / 2), n / 2);
71 | } else {
72 | return THEFT_NO_MORE_TACTICS;
73 | }
74 | }
75 |
76 | static struct theft_type_info string_info = {
77 | .alloc = string_alloc_cb,
78 | .free = string_free_cb,
79 | .print = string_print_cb,
80 | .hash = string_hash_cb,
81 | .shrink = string_shrink_cb,
82 | };
83 |
84 | static theft_trial_res prop_should_return_results_if_there_is_a_match(char *needle,
85 | char *haystack) {
86 | int match_exists = has_match(needle, haystack);
87 | if (!match_exists)
88 | return THEFT_TRIAL_SKIP;
89 |
90 | score_t score = match(needle, haystack);
91 |
92 | if (needle[0] == '\0')
93 | return THEFT_TRIAL_SKIP;
94 |
95 | if (score == SCORE_MIN)
96 | return THEFT_TRIAL_FAIL;
97 |
98 | return THEFT_TRIAL_PASS;
99 | }
100 |
101 | TEST should_return_results_if_there_is_a_match() {
102 | struct theft *t = theft_init(0);
103 | struct theft_cfg cfg = {
104 | .name = __func__,
105 | .fun = prop_should_return_results_if_there_is_a_match,
106 | .type_info = {&string_info, &string_info},
107 | .trials = 100000,
108 | };
109 |
110 | theft_run_res res = theft_run(t, &cfg);
111 | theft_free(t);
112 | GREATEST_ASSERT_EQm("should_return_results_if_there_is_a_match", THEFT_RUN_PASS, res);
113 | PASS();
114 | }
115 |
116 | static theft_trial_res prop_positions_should_match_characters_in_string(char *needle,
117 | char *haystack) {
118 | int match_exists = has_match(needle, haystack);
119 | if (!match_exists)
120 | return THEFT_TRIAL_SKIP;
121 |
122 | int n = strlen(needle);
123 | size_t *positions = calloc(n, sizeof(size_t));
124 | if (!positions)
125 | return THEFT_TRIAL_ERROR;
126 |
127 | match_positions(needle, haystack, positions);
128 |
129 | /* Must be increasing */
130 | for (int i = 1; i < n; i++) {
131 | if (positions[i] <= positions[i - 1]) {
132 | return THEFT_TRIAL_FAIL;
133 | }
134 | }
135 |
136 | /* Matching characters must be in returned positions */
137 | for (int i = 0; i < n; i++) {
138 | if (toupper(needle[i]) != toupper(haystack[positions[i]])) {
139 | return THEFT_TRIAL_FAIL;
140 | }
141 | }
142 |
143 | free(positions);
144 | return THEFT_TRIAL_PASS;
145 | }
146 |
147 | TEST positions_should_match_characters_in_string() {
148 | struct theft *t = theft_init(0);
149 | struct theft_cfg cfg = {
150 | .name = __func__,
151 | .fun = prop_positions_should_match_characters_in_string,
152 | .type_info = {&string_info, &string_info},
153 | .trials = 100000,
154 | };
155 |
156 | theft_run_res res = theft_run(t, &cfg);
157 | theft_free(t);
158 | GREATEST_ASSERT_EQm("should_return_results_if_there_is_a_match", THEFT_RUN_PASS, res);
159 | PASS();
160 | }
161 |
162 | SUITE(properties_suite) {
163 | RUN_TEST(should_return_results_if_there_is_a_match);
164 | RUN_TEST(positions_should_match_characters_in_string);
165 | }
166 |
--------------------------------------------------------------------------------