pax_global_header00006660000000000000000000000064133520071030014504gustar00rootroot0000000000000052 comment=f5d41e726cd997ba038815e218c48017228a8f0e fzy-1.0/000077500000000000000000000000001335200710300121545ustar00rootroot00000000000000fzy-1.0/.clang-format000066400000000000000000000002101335200710300145200ustar00rootroot00000000000000IndentWidth: 8 UseTab: Always BreakBeforeBraces: Attach IndentCaseLabels: true AllowShortFunctionsOnASingleLine: false ColumnLimit: 100 fzy-1.0/.gitignore000066400000000000000000000000671335200710300141470ustar00rootroot00000000000000fzy fzytest *.o config.h test/acceptance/vendor/bundle fzy-1.0/.travis.yml000066400000000000000000000004441335200710300142670ustar00rootroot00000000000000dist: trusty sudo: false language: c compiler: - clang - gcc os: - linux - osx script: make && make check jobs: include: - stage: Acceptance Test language: ruby rvm: 2.5.1 script: make acceptance addons: apt: packages: - tmux fzy-1.0/ALGORITHM.md000066400000000000000000000152001335200710300140220ustar00rootroot00000000000000 This document describes the scoring algorithm of fzy as well as the algorithm of other similar projects. # Matching vs Scoring I like to split the problem a fuzzy matchers into two subproblems: matching and scoring. Matching determines which results are eligible for the list. All the projects here consider this to be the same problem, matching the candidate strings against the search string with any number of gaps. Scoring determines the order in which the results are sorted. Since scoring is tasked with finding what the human user intended, there is no correct solution. As a result there are large variety in scoring strategies. # fzy's matching Generally, more time is taken in matching rather than scoring, so it is important that matching be as fast as possible. If this were case sensitive it would be a simple loop calling strchr, but since it needs to be case insensitive. # fzy's scoring fzy treats scoring as a modified [edit distance](https://en.wikipedia.org/wiki/Edit_distance) problem of calculating the [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance). Edit distance is the measure of how different two strings are in terms of insertions, deletions, and substitutions. This is the same problems as [DNA sequence alignment](https://en.wikipedia.org/wiki/Sequence_alignment). Fuzzy matching is a simpler problem which only accepts insertions, not deletions or substitutions. fzy's scoring is a dynamic programming algorithm similar to [Wagner–Fischer](https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm) and [Needleman–Wunsch](https://en.wikipedia.org/wiki/Needleman%E2%80%93Wunsch_algorithm). Dynamic programming requires the observation that the result is based on the result of subproblems. Fzy borrows heavily from concepts in bioinformatics to performs scoring. Fzy builds a `n`-by-`m` matrix, where `n` is the length of the search string and `m` the length of the candidate string. Each position `(i,j)` in the matrix stores the score for matching the first `i` characters of the search with the first `j` characters of the candidate. Fzy calculates an affine gap penalty, this means simply that we assign a constant penalty for having a gap and a linear penalty for the length of the gap. Inspired by the [Gotoh algorithm (pdf)](http://www.cs.unibo.it/~dilena/LabBII/Papers/AffineGaps.pdf), fzy computes a second `D` (for diagonal) matrix in parallel with the score matrix. The `D` matrix computes the best score which *ends* in a match. This allows both computation of the penalty for starting a gap and the score for a consecutive match. Using [this algorithm](https://github.com/jhawthorn/fzy/blob/master/src/match.c#L58) fzy is able to score based on the optimal match. * Gaps (negative score) * at the start of the match * at the end of the match * within the match * Matches (positive score) * consecutive * following a slash * following a space, underscore, or dash (the start of a word) * capital letter (the start of a CamelCase word) * following a dot (often a file extension) # Other fuzzy finders ## TextMate TextMate deserves immense credit for popularizing fuzzy finding from inside text editors. It's influence can be found in the command-t project, various other editors use command-t for file finding, and the 't' command in the github web interface. * https://github.com/textmate/textmate/blob/master/Frameworks/text/src/ranker.cc ## command-t, ctrlp-cmatcher Command is a plugin first released in 2010 intending to bring TextMate's "Go to File" feature to vim. Anecdotally, this algorithm works very well. The recursive nature makes it a little hard to The wy `last_idx` is suspicious. * https://github.com/wincent/command-t/blob/master/ruby/command-t/match.c * https://github.com/JazzCore/ctrlp-cmatcher/blob/master/autoload/fuzzycomt.c ## Length of shortest first match: fzf https://github.com/junegunn/fzf/blob/master/src/algo/algo.go Fzy scores based on the size of the greedy shortest match. fzf finds its match by the first match appearing in the candidate string. It has some cleverness to find if there is a shorter match contained in that search, but it isn't guaranteed to find the shortest match in the string. Example results for the search "abc" * **AXXBXXC**xxabc * xxxxxxx**AXBXC** * xxxxxxxxx**ABC** ## Length of first match: ctrlp, pick, selecta (`<= 0.0.6`) These score based on the length of the first match in the candidate. This is probably the simplest useful algorithm. This has the advantage that the heavy lifting can be performed by the regex engine, which is faster than implementing anything natively in ruby or Vim script. ## Length of shortest match: pick Pick has a method, `min_match`, to find the absolute shortest match in a string. This will find better results than the finders, at the expense of speed, as backtracking is required. ## selecta (latest master) https://github.com/garybernhardt/selecta/commit/d874c99dd7f0f94225a95da06fc487b0fa5b9edc https://github.com/garybernhardt/selecta/issues/80 Selecta doesn't compare all possible matches, but only the shortest match from the same start location. This can lead to inconsistent results. Example results for the search "abc" * x**AXXXXBC** * x**ABXC**x * x**ABXC**xbc The third result here should have been scored the same as the first, but the lower scoring but shorter match is what is measured. ## others * https://github.com/joshaven/string_score/blob/master/coffee/string_score.coffee (first match + heuristics) * https://github.com/atom/fuzzaldrin/blob/master/src/scorer.coffee (modified version of string_score) * https://github.com/jeancroy/fuzzaldrin-plus/blob/master/src/scorer.coffee (Smith Waterman) # Possible fzy Algorithm Improvements ## Case sensitivity fzy currently treats all searches as case-insensitive. However, scoring prefers matches on uppercase letters to help find CamelCase candidates. It might be desirable to support a case sensitive flag or "smart case" searching. ## Faster matching Matching is currently performed using the standard lib's `strpbrk`, which has a very simple implementation (at least in glibc). Glibc has an extremely clever `strchr` implementation which searches the haystack string by [word](https://en.wikipedia.org/wiki/Word_(computer_architecture)), a 4 or 8 byte `long int`, instead of by byte. It tests if a word is likely to contain either the search char or the null terminator using bit twiddling. A similar method could probably be written to perform to find a character in a string case-insensitively. * https://sourceware.org/git/?p=glibc.git;a=blob;f=string/strchr.c;h=f73891d439dcd8a08954fad4d4615acac4e0eb85;hb=HEAD fzy-1.0/CHANGELOG.md000066400000000000000000000032221335200710300137640ustar00rootroot00000000000000## 1.0 (2018-09-23) Features: - Support UTF-8 - Support readline-like editing - Quit on Esc - Redraw on terminal resize - Bracketed paste escapes are ignored Performance: - Initialize tty interface before reading stdin ## 0.9 (2017-04-17) Features: - Support Ctrl-k and Ctrl-j for movement Performance: - Use threads to parallelize sorting - Improve parallelism of searching and scoring Internal: - Fix test suite on i386 - Replace test suite with greatest - Add property tests - Add acceptance tests ## 0.8 (2017-01-01) Bugfixes: - Fix cursor position shifing upwards when input has less than 2 items. ## 0.7 (2016-08-03) Bugfixes: - Fixed a segfault when encountering non-ascii characters - Fixed building against musl libc ## 0.6 (2016-07-26) Performance: - Use threads to parallelize searching and scoring - Read all pending input from tty before searching - Use a lookup table for computing bonuses Bugfixes: - Fixed command line parsing on ARM - Fix error when autocompleting and there are no matches ## 0.5 (2016-06-11) Bugfixes: - Made sorting stable on all platforms ## 0.4 (May 19, 2016) Features: - Add `-q`/`--query` for specifying initial query Bugfixes: - Fixed last line of results not being cleared on exit - Check errors when opening the TTY device ## 0.3 (April 25, 2016) Bugfixes: - Runs properly in a terminal with -icrnl ## 0.2 (October 19, 2014) Features: - Allow specifying custom prompt Performance: - Reduce memory usage on large sets Bugfixes: - Terminal is properly reset on exit - Fixed make install on OS X ## 0.1 (September 20, 2014) Initial release fzy-1.0/LICENSE000066400000000000000000000020701335200710300131600ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 John Hawthorn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fzy-1.0/Makefile000066400000000000000000000026571335200710300136260ustar00rootroot00000000000000VERSION=1.0 CPPFLAGS=-DVERSION=\"${VERSION}\" -D_GNU_SOURCE CFLAGS+=-Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps PREFIX?=/usr/local MANDIR?=$(PREFIX)/share/man BINDIR?=$(PREFIX)/bin DEBUGGER?= INSTALL=install INSTALL_PROGRAM=$(INSTALL) INSTALL_DATA=${INSTALL} -m 644 LIBS=-lpthread OBJECTS=src/fzy.o src/match.o src/tty.o src/choices.o src/options.o src/tty_interface.o THEFTDEPS = deps/theft/theft.o deps/theft/theft_bloom.o deps/theft/theft_mt.o deps/theft/theft_hash.o 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) all: fzy test/fzytest: $(TESTOBJECTS) $(CC) $(CFLAGS) $(CCFLAGS) -Isrc -o $@ $(TESTOBJECTS) $(LIBS) acceptance: fzy cd test/acceptance && bundle --quiet && bundle exec ruby acceptance_test.rb test: check check: test/fzytest $(DEBUGGER) ./test/fzytest fzy: $(OBJECTS) $(CC) $(CFLAGS) $(CCFLAGS) -o $@ $(OBJECTS) $(LIBS) %.o: %.c config.h $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< config.h: cp src/config.def.h config.h install: fzy mkdir -p $(DESTDIR)$(BINDIR) cp fzy $(DESTDIR)$(BINDIR)/ chmod 755 ${DESTDIR}${BINDIR}/fzy mkdir -p $(DESTDIR)$(MANDIR)/man1 cp fzy.1 $(DESTDIR)$(MANDIR)/man1/ chmod 644 ${DESTDIR}${MANDIR}/man1/fzy.1 fmt: clang-format -i src/*.c src/*.h clean: rm -f fzy test/fzytest src/*.o deps/*/*.o veryclean: clean rm -f config.h .PHONY: test check all clean veryclean install fmt acceptance fzy-1.0/README.md000066400000000000000000000075741335200710300134500ustar00rootroot00000000000000![fzy](http://i.hawth.ca/u/fzy-github.svg) **fzy** is a fast, simple fuzzy text selector for the terminal with an advanced [scoring algorithm](#sorting). ![](http://i.hawth.ca/u/fzy_animated_demo.svg)
It's been kind of life-changing. -@graygilmore
fzy works great btw -@alexblackie
[![Build Status](https://travis-ci.org/jhawthorn/fzy.svg?branch=master)](https://travis-ci.org/jhawthorn/fzy) ## Why use this over fzf, pick, selecta, ctrlp, ...? fzy is faster and shows better results than other fuzzy finders. Most other fuzzy matchers sort based on the length of a match. fzy tries to find the result the user intended. It does this by favouring matches on consecutive letters and starts of words. This allows matching using acronyms or different parts of the path. A gory comparison of the sorting used by fuzzy finders can be found in [ALGORITHM.md](ALGORITHM.md) fzy is designed to be used both as an editor plugin and on the command line. Rather than clearing the screen, fzy displays its interface directly below the current cursor position, scrolling the screen if necessary. ## Installation **macOS** Using Homebrew brew install fzy Using MacPorts sudo port install fzy **[Arch Linux](https://www.archlinux.org/packages/?sort=&q=fzy&maintainer=&flagged=)/MSYS2**: `pacman -S fzy` **[FreeBSD](https://www.freebsd.org/cgi/ports.cgi?query=fzy&stype=all)**: `pkg install fzy` **[Gentoo Linux](https://packages.gentoo.org/packages/app-shells/fzy)**: `emerge -av app-shells/fzy` **[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` **[pkgsrc](http://pkgsrc.se/misc/fzy) (NetBSD and others)**: `pkgin install fzy` **[openSUSE](https://software.opensuse.org/package/fzy)**: `zypper in fzy` ### From source make sudo make install The `PREFIX` environment variable can be used to specify the install location, the default is `/usr/local`. ## Usage 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). ### Use with Vim fzy can be easily integrated with vim. ``` vim function! FzyCommand(choice_command, vim_command) try let output = system(a:choice_command . " | fzy ") catch /Vim:Interrupt/ " Swallow errors from ^C, allow redraw! below endtry redraw! if v:shell_error == 0 && !empty(output) exec a:vim_command . ' ' . output endif endfunction nnoremap e :call FzyCommand("find -type f", ":e") nnoremap v :call FzyCommand("find -type f", ":vs") nnoremap s :call FzyCommand("find -type f", ":sp") ``` 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`. ``` vim nnoremap e :call FzyCommand("ag . --silent -l -g ''", ":e") nnoremap v :call FzyCommand("ag . --silent -l -g ''", ":vs") nnoremap s :call FzyCommand("ag . --silent -l -g ''", ":sp") ``` ## Sorting fzy attempts to present the best matches first. The following considerations are weighted when sorting: It prefers consecutive characters: `file` will match file over filter. It prefers matching the beginning of words: `amp` is likely to match app/models/posts.rb. It prefers shorter matches: `abce` matches abcdef over abc de. It prefers shorter candidates: `test` matches tests over testing. fzy-1.0/contrib/000077500000000000000000000000001335200710300136145ustar00rootroot00000000000000fzy-1.0/contrib/fzy-dvtm000077500000000000000000000020531335200710300153220ustar00rootroot00000000000000#!/bin/sh _echo() { printf %s\\n "$*" } fatal() { _echo "$*" >&2 exit 1 } main() { if [ -z "${DVTM_CMD_FIFO}" ]; then fatal "No DVTM_CMD_FIFO variable detected in the environment" fi readonly PATH_DIR_TMP=$(mktemp -d) readonly PATH_FIFO_IN="${PATH_DIR_TMP}/in" readonly PATH_FIFO_OUT="${PATH_DIR_TMP}/out" readonly PATH_FIFO_RET="${PATH_DIR_TMP}/ret" if [ -z "${PATH_DIR_TMP}" ]; then fatal "Unable to create a temporary directory" fi args="" for i in "$@"; do if [ -z "${args}" ]; then args="\\'${i}\\'" else args="${args} \\'${i}\\'" fi done mkfifo "${PATH_FIFO_IN}" "${PATH_FIFO_OUT}" _echo \ "create 'fzy ${args} < \\'${PATH_FIFO_IN}\\' > \\'${PATH_FIFO_OUT}\\' 2>&1; echo $? > \\'${PATH_FIFO_RET}\\''" \ > "${DVTM_CMD_FIFO}" cat <&0 > "${PATH_FIFO_IN}" & cat < "${PATH_FIFO_OUT}" readonly CODE_RET=$(head -n 1 "${PATH_FIFO_RET}") rm -rf "${PATH_DIR_TMP}" exit "${CODE_RET}" } main "$@" fzy-1.0/contrib/fzy-tmux000077500000000000000000000017521335200710300153520ustar00rootroot00000000000000#!/bin/sh _echo() { printf %s\\n "$*" } fatal() { _echo "$*" >&2 exit 1 } main() { if [ -z "${TMUX}" ]; then fatal "No TMUX variable detected in the environment" fi readonly PATH_DIR_TMP=$(mktemp -d) readonly PATH_FIFO_IN="${PATH_DIR_TMP}/in" readonly PATH_FIFO_OUT="${PATH_DIR_TMP}/out" readonly PATH_FIFO_RET="${PATH_DIR_TMP}/ret" if [ -z "${PATH_DIR_TMP}" ]; then fatal "Unable to create a temporary directory" fi mkfifo "${PATH_FIFO_IN}" "${PATH_FIFO_OUT}" export TMUX=$(_echo "${TMUX}" | cut -d , -f 1,2) eval "tmux \ set-window-option synchronize-panes off \\; \ set-window-option remain-on-exit off \\; \ split-window \"fzy $* < '${PATH_FIFO_IN}' > '${PATH_FIFO_OUT}' 2>&1; echo $? > '${PATH_FIFO_RET}'\"" cat <&0 > "${PATH_FIFO_IN}" & cat < "${PATH_FIFO_OUT}" readonly CODE_RET=$(head -n 1 "${PATH_FIFO_RET}") rm -rf "${PATH_DIR_TMP}" exit "${CODE_RET}" } main "$@" fzy-1.0/deps/000077500000000000000000000000001335200710300131075ustar00rootroot00000000000000fzy-1.0/deps/greatest/000077500000000000000000000000001335200710300147255ustar00rootroot00000000000000fzy-1.0/deps/greatest/greatest.h000066400000000000000000001746061335200710300167320ustar00rootroot00000000000000/* * Copyright (c) 2011-2017 Scott Vokes * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef GREATEST_H #define GREATEST_H #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) extern "C" { #endif /* 1.3.1 */ #define GREATEST_VERSION_MAJOR 1 #define GREATEST_VERSION_MINOR 3 #define GREATEST_VERSION_PATCH 1 /* A unit testing system for C, contained in 1 file. * It doesn't use dynamic allocation or depend on anything * beyond ANSI C89. * * An up-to-date version can be found at: * https://github.com/silentbicycle/greatest/ */ /********************************************************************* * Minimal test runner template *********************************************************************/ #if 0 #include "greatest.h" TEST foo_should_foo(void) { PASS(); } static void setup_cb(void *data) { printf("setup callback for each test case\n"); } static void teardown_cb(void *data) { printf("teardown callback for each test case\n"); } SUITE(suite) { /* Optional setup/teardown callbacks which will be run before/after * every test case. If using a test suite, they will be cleared when * the suite finishes. */ SET_SETUP(setup_cb, voidp_to_callback_data); SET_TEARDOWN(teardown_cb, voidp_to_callback_data); RUN_TEST(foo_should_foo); } /* Add definitions that need to be in the test runner's main file. */ GREATEST_MAIN_DEFS(); /* Set up, run suite(s) of tests, report pass/fail/skip stats. */ int run_tests(void) { GREATEST_INIT(); /* init. greatest internals */ /* List of suites to run (if any). */ RUN_SUITE(suite); /* Tests can also be run directly, without using test suites. */ RUN_TEST(foo_should_foo); GREATEST_PRINT_REPORT(); /* display results */ return greatest_all_passed(); } /* main(), for a standalone command-line test runner. * This replaces run_tests above, and adds command line option * handling and exiting with a pass/fail status. */ int main(int argc, char **argv) { GREATEST_MAIN_BEGIN(); /* init & parse command-line args */ RUN_SUITE(suite); GREATEST_MAIN_END(); /* display results */ } #endif /*********************************************************************/ #include #include #include #include /*********** * Options * ***********/ /* Default column width for non-verbose output. */ #ifndef GREATEST_DEFAULT_WIDTH #define GREATEST_DEFAULT_WIDTH 72 #endif /* FILE *, for test logging. */ #ifndef GREATEST_STDOUT #define GREATEST_STDOUT stdout #endif /* Remove GREATEST_ prefix from most commonly used symbols? */ #ifndef GREATEST_USE_ABBREVS #define GREATEST_USE_ABBREVS 1 #endif /* Set to 0 to disable all use of setjmp/longjmp. */ #ifndef GREATEST_USE_LONGJMP #define GREATEST_USE_LONGJMP 1 #endif /* Make it possible to replace fprintf with another * function with the same interface. */ #ifndef GREATEST_FPRINTF #define GREATEST_FPRINTF fprintf #endif #if GREATEST_USE_LONGJMP #include #endif /* Set to 0 to disable all use of time.h / clock(). */ #ifndef GREATEST_USE_TIME #define GREATEST_USE_TIME 1 #endif #if GREATEST_USE_TIME #include #endif /* Floating point type, for ASSERT_IN_RANGE. */ #ifndef GREATEST_FLOAT #define GREATEST_FLOAT double #define GREATEST_FLOAT_FMT "%g" #endif /********* * Types * *********/ /* Info for the current running suite. */ typedef struct greatest_suite_info { unsigned int tests_run; unsigned int passed; unsigned int failed; unsigned int skipped; #if GREATEST_USE_TIME /* timers, pre/post running suite and individual tests */ clock_t pre_suite; clock_t post_suite; clock_t pre_test; clock_t post_test; #endif } greatest_suite_info; /* Type for a suite function. */ typedef void greatest_suite_cb(void); /* Types for setup/teardown callbacks. If non-NULL, these will be run * and passed the pointer to their additional data. */ typedef void greatest_setup_cb(void *udata); typedef void greatest_teardown_cb(void *udata); /* Type for an equality comparison between two pointers of the same type. * Should return non-0 if equal, otherwise 0. * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ typedef int greatest_equal_cb(const void *exp, const void *got, void *udata); /* Type for a callback that prints a value pointed to by T. * Return value has the same meaning as printf's. * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ typedef int greatest_printf_cb(const void *t, void *udata); /* Callbacks for an arbitrary type; needed for type-specific * comparisons via GREATEST_ASSERT_EQUAL_T[m].*/ typedef struct greatest_type_info { greatest_equal_cb *equal; greatest_printf_cb *print; } greatest_type_info; typedef struct greatest_memory_cmp_env { const unsigned char *exp; const unsigned char *got; size_t size; } greatest_memory_cmp_env; /* Callbacks for string and raw memory types. */ extern greatest_type_info greatest_type_info_string; extern greatest_type_info greatest_type_info_memory; typedef enum { GREATEST_FLAG_FIRST_FAIL = 0x01, GREATEST_FLAG_LIST_ONLY = 0x02 } greatest_flag_t; /* Internal state for a PRNG, used to shuffle test order. */ struct greatest_prng { unsigned char random_order; /* use random ordering? */ unsigned char initialized; /* is random ordering initialized? */ unsigned char pad_0[2]; unsigned long state; /* PRNG state */ unsigned long count; /* how many tests, this pass */ unsigned long count_ceil; /* total number of tests */ unsigned long count_run; /* total tests run */ unsigned long mod; /* power-of-2 ceiling of count_ceil */ unsigned long a; /* LCG multiplier */ unsigned long c; /* LCG increment */ }; /* Struct containing all test runner state. */ typedef struct greatest_run_info { unsigned char flags; unsigned char verbosity; unsigned char pad_0[2]; unsigned int tests_run; /* total test count */ /* currently running test suite */ greatest_suite_info suite; /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; /* info to print about the most recent failure */ unsigned int fail_line; unsigned int pad_1; const char *fail_file; const char *msg; /* current setup/teardown hooks and userdata */ greatest_setup_cb *setup; void *setup_udata; greatest_teardown_cb *teardown; void *teardown_udata; /* formatting info for ".....s...F"-style output */ unsigned int col; unsigned int width; /* only run a specific suite or test */ const char *suite_filter; const char *test_filter; const char *test_exclude; struct greatest_prng prng[2]; /* 0: suites, 1: tests */ #if GREATEST_USE_TIME /* overall timers */ clock_t begin; clock_t end; #endif #if GREATEST_USE_LONGJMP int pad_jmp_buf; jmp_buf jump_dest; #endif } greatest_run_info; struct greatest_report_t { /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; }; /* Global var for the current testing context. * Initialized by GREATEST_MAIN_DEFS(). */ extern greatest_run_info greatest_info; /* Type for ASSERT_ENUM_EQ's ENUM_STR argument. */ typedef const char *greatest_enum_str_fun(int value); /********************** * Exported functions * **********************/ /* These are used internally by greatest. */ void greatest_do_pass(const char *name); void greatest_do_fail(const char *name); void greatest_do_skip(const char *name); int greatest_suite_pre(const char *suite_name); void greatest_suite_post(void); int greatest_test_pre(const char *name); void greatest_test_post(const char *name, int res); void greatest_usage(const char *name); int greatest_do_assert_equal_t(const void *exp, const void *got, greatest_type_info *type_info, void *udata); void greatest_prng_init_first_pass(int id); int greatest_prng_init_second_pass(int id, unsigned long seed); void greatest_prng_step(int id); /* These are part of the public greatest API. */ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); int greatest_all_passed(void); void greatest_set_suite_filter(const char *filter); void greatest_set_test_filter(const char *filter); void greatest_set_test_exclude(const char *filter); void greatest_stop_at_first_fail(void); void greatest_get_report(struct greatest_report_t *report); unsigned int greatest_get_verbosity(void); void greatest_set_verbosity(unsigned int verbosity); void greatest_set_flag(greatest_flag_t flag); /******************** * Language Support * ********************/ /* If __VA_ARGS__ (C99) is supported, allow parametric testing * without needing to manually manage the argument struct. */ #if __STDC_VERSION__ >= 19901L || _MSC_VER >= 1800 #define GREATEST_VA_ARGS #endif /********** * Macros * **********/ /* Define a suite. */ #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void) /* Declare a suite, provided by another compilation unit. */ #define GREATEST_SUITE_EXTERN(NAME) void NAME(void) /* Start defining a test function. * The arguments are not included, to allow parametric testing. */ #define GREATEST_TEST static enum greatest_test_res /* PASS/FAIL/SKIP result from a test. Used internally. */ typedef enum greatest_test_res { GREATEST_TEST_RES_PASS = 0, GREATEST_TEST_RES_FAIL = -1, GREATEST_TEST_RES_SKIP = 1 } greatest_test_res; /* Run a suite. */ #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) /* Run a test in the current suite. */ #define GREATEST_RUN_TEST(TEST) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(); \ } \ greatest_test_post(#TEST, res); \ } \ } while (0) /* Ignore a test, don't warn about it being unused. */ #define GREATEST_IGNORE_TEST(TEST) (void)TEST /* Run a test in the current suite with one void * argument, * which can be a pointer to a struct with multiple arguments. */ #define GREATEST_RUN_TEST1(TEST, ENV) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(ENV); \ } \ greatest_test_post(#TEST, res); \ } \ } while (0) #ifdef GREATEST_VA_ARGS #define GREATEST_RUN_TESTp(TEST, ...) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(__VA_ARGS__); \ } \ greatest_test_post(#TEST, res); \ } \ } while (0) #endif /* Check if the test runner is in verbose mode. */ #define GREATEST_IS_VERBOSE() ((greatest_info.verbosity) > 0) #define GREATEST_LIST_ONLY() \ (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) #define GREATEST_FIRST_FAIL() \ (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) #define GREATEST_FAILURE_ABORT() \ (GREATEST_FIRST_FAIL() && \ (greatest_info.suite.failed > 0 || greatest_info.failed > 0)) /* Message-less forms of tests defined below. */ #define GREATEST_PASS() GREATEST_PASSm(NULL) #define GREATEST_FAIL() GREATEST_FAILm(NULL) #define GREATEST_SKIP() GREATEST_SKIPm(NULL) #define GREATEST_ASSERT(COND) \ GREATEST_ASSERTm(#COND, COND) #define GREATEST_ASSERT_OR_LONGJMP(COND) \ GREATEST_ASSERT_OR_LONGJMPm(#COND, COND) #define GREATEST_ASSERT_FALSE(COND) \ GREATEST_ASSERT_FALSEm(#COND, COND) #define GREATEST_ASSERT_EQ(EXP, GOT) \ GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \ GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT) #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \ GREATEST_ASSERT_IN_RANGEm(#EXP " != " #GOT " +/- " #TOL, EXP, GOT, TOL) #define GREATEST_ASSERT_EQUAL_T(EXP, GOT, TYPE_INFO, UDATA) \ GREATEST_ASSERT_EQUAL_Tm(#EXP " != " #GOT, EXP, GOT, TYPE_INFO, UDATA) #define GREATEST_ASSERT_STR_EQ(EXP, GOT) \ GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) #define GREATEST_ASSERT_STRN_EQ(EXP, GOT, SIZE) \ GREATEST_ASSERT_STRN_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) #define GREATEST_ASSERT_MEM_EQ(EXP, GOT, SIZE) \ GREATEST_ASSERT_MEM_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) #define GREATEST_ASSERT_ENUM_EQ(EXP, GOT, ENUM_STR) \ GREATEST_ASSERT_ENUM_EQm(#EXP " != " #GOT, EXP, GOT, ENUM_STR) /* The following forms take an additional message argument first, * to be displayed by the test runner. */ /* Fail if a condition is not true, with message. */ #define GREATEST_ASSERTm(MSG, COND) \ do { \ greatest_info.assertions++; \ if (!(COND)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if a condition is not true, longjmping out of test. */ #define GREATEST_ASSERT_OR_LONGJMPm(MSG, COND) \ do { \ greatest_info.assertions++; \ if (!(COND)) { GREATEST_FAIL_WITH_LONGJMPm(MSG); } \ } while (0) /* Fail if a condition is not false, with message. */ #define GREATEST_ASSERT_FALSEm(MSG, COND) \ do { \ greatest_info.assertions++; \ if ((COND)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if EXP != GOT (equality comparison by ==). */ #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ do { \ greatest_info.assertions++; \ if ((EXP) != (GOT)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if EXP != GOT (equality comparison by ==). * Warning: FMT, EXP, and GOT will be evaluated more * than once on failure. */ #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ do { \ greatest_info.assertions++; \ if ((EXP) != (GOT)) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ GREATEST_FPRINTF(GREATEST_STDOUT, FMT, EXP); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ GREATEST_FPRINTF(GREATEST_STDOUT, FMT, GOT); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ GREATEST_FAILm(MSG); \ } \ } while (0) /* Fail if EXP is not equal to GOT, printing enum IDs. */ #define GREATEST_ASSERT_ENUM_EQm(MSG, EXP, GOT, ENUM_STR) \ do { \ int greatest_EXP = (int)(EXP); \ int greatest_GOT = (int)(GOT); \ greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \ if (greatest_EXP != greatest_GOT) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: %s", \ greatest_ENUM_STR(greatest_EXP)); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: %s\n", \ greatest_ENUM_STR(greatest_GOT)); \ GREATEST_FAILm(MSG); \ } \ } while (0) \ /* Fail if GOT not in range of EXP +|- TOL. */ #define GREATEST_ASSERT_IN_RANGEm(MSG, EXP, GOT, TOL) \ do { \ GREATEST_FLOAT greatest_EXP = (EXP); \ GREATEST_FLOAT greatest_GOT = (GOT); \ GREATEST_FLOAT greatest_TOL = (TOL); \ greatest_info.assertions++; \ if ((greatest_EXP > greatest_GOT && \ greatest_EXP - greatest_GOT > greatest_TOL) || \ (greatest_EXP < greatest_GOT && \ greatest_GOT - greatest_EXP > greatest_TOL)) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\nExpected: " GREATEST_FLOAT_FMT \ " +/- " GREATEST_FLOAT_FMT \ "\n Got: " GREATEST_FLOAT_FMT \ "\n", \ greatest_EXP, greatest_TOL, greatest_GOT); \ GREATEST_FAILm(MSG); \ } \ } while (0) /* Fail if EXP is not equal to GOT, according to strcmp. */ #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ do { \ GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ &greatest_type_info_string, NULL); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to strcmp. */ #define GREATEST_ASSERT_STRN_EQm(MSG, EXP, GOT, SIZE) \ do { \ size_t size = SIZE; \ GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ &greatest_type_info_string, &size); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to memcmp. */ #define GREATEST_ASSERT_MEM_EQm(MSG, EXP, GOT, SIZE) \ do { \ greatest_memory_cmp_env env; \ env.exp = (const unsigned char *)EXP; \ env.got = (const unsigned char *)GOT; \ env.size = SIZE; \ GREATEST_ASSERT_EQUAL_Tm(MSG, env.exp, env.got, \ &greatest_type_info_memory, &env); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to a comparison * callback in TYPE_INFO. If they are not equal, optionally use a * print callback in TYPE_INFO to print them. */ #define GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, TYPE_INFO, UDATA) \ do { \ greatest_type_info *type_info = (TYPE_INFO); \ greatest_info.assertions++; \ if (!greatest_do_assert_equal_t(EXP, GOT, \ type_info, UDATA)) { \ if (type_info == NULL || type_info->equal == NULL) { \ GREATEST_FAILm("type_info->equal callback missing!"); \ } else { \ GREATEST_FAILm(MSG); \ } \ } \ } while (0) \ /* Pass. */ #define GREATEST_PASSm(MSG) \ do { \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_PASS; \ } while (0) /* Fail. */ #define GREATEST_FAILm(MSG) \ do { \ greatest_info.fail_file = __FILE__; \ greatest_info.fail_line = __LINE__; \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_FAIL; \ } while (0) /* Optional GREATEST_FAILm variant that longjmps. */ #if GREATEST_USE_LONGJMP #define GREATEST_FAIL_WITH_LONGJMP() GREATEST_FAIL_WITH_LONGJMPm(NULL) #define GREATEST_FAIL_WITH_LONGJMPm(MSG) \ do { \ greatest_info.fail_file = __FILE__; \ greatest_info.fail_line = __LINE__; \ greatest_info.msg = MSG; \ longjmp(greatest_info.jump_dest, GREATEST_TEST_RES_FAIL); \ } while (0) #endif /* Skip the current test. */ #define GREATEST_SKIPm(MSG) \ do { \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_SKIP; \ } while (0) /* Check the result of a subfunction using ASSERT, etc. */ #define GREATEST_CHECK_CALL(RES) \ do { \ enum greatest_test_res greatest_RES = RES; \ if (greatest_RES != GREATEST_TEST_RES_PASS) { \ return greatest_RES; \ } \ } while (0) \ #if GREATEST_USE_TIME #define GREATEST_SET_TIME(NAME) \ NAME = clock(); \ if (NAME == (clock_t) -1) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "clock error: %s\n", #NAME); \ exit(EXIT_FAILURE); \ } #define GREATEST_CLOCK_DIFF(C1, C2) \ GREATEST_FPRINTF(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ (long unsigned int) (C2) - (long unsigned int)(C1), \ (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) #else #define GREATEST_SET_TIME(UNUSED) #define GREATEST_CLOCK_DIFF(UNUSED1, UNUSED2) #endif #if GREATEST_USE_LONGJMP #define GREATEST_SAVE_CONTEXT() \ /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call * \ * so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ ((enum greatest_test_res)(setjmp(greatest_info.jump_dest))) #else #define GREATEST_SAVE_CONTEXT() \ /*a no-op, since setjmp/longjmp aren't being used */ \ GREATEST_TEST_RES_PASS #endif /* Run every suite / test function run within BODY in pseudo-random * order, seeded by SEED. (The top 3 bits of the seed are ignored.) * * This should be called like: * GREATEST_SHUFFLE_TESTS(seed, { * GREATEST_RUN_TEST(some_test); * GREATEST_RUN_TEST(some_other_test); * GREATEST_RUN_TEST(yet_another_test); * }); * * Note that the body of the second argument will be evaluated * multiple times. */ #define GREATEST_SHUFFLE_SUITES(SD, BODY) GREATEST_SHUFFLE(0, SD, BODY) #define GREATEST_SHUFFLE_TESTS(SD, BODY) GREATEST_SHUFFLE(1, SD, BODY) #define GREATEST_SHUFFLE(ID, SD, BODY) \ do { \ struct greatest_prng *prng = &greatest_info.prng[ID]; \ greatest_prng_init_first_pass(ID); \ do { \ prng->count = 0; \ if (prng->initialized) { greatest_prng_step(ID); } \ BODY; \ if (!prng->initialized) { \ if (!greatest_prng_init_second_pass(ID, SD)) { break; } \ } else if (prng->count_run == prng->count_ceil) { \ break; \ } \ } while (!GREATEST_FAILURE_ABORT()); \ prng->count_run = prng->random_order = prng->initialized = 0; \ } while(0) /* Include several function definitions in the main test file. */ #define GREATEST_MAIN_DEFS() \ \ /* Is FILTER a subset of NAME? */ \ static int greatest_name_match(const char *name, const char *filter, \ int res_if_none) { \ size_t offset = 0; \ size_t filter_len = filter ? strlen(filter) : 0; \ if (filter_len == 0) { return res_if_none; } /* no filter */ \ while (name[offset] != '\0') { \ if (name[offset] == filter[0]) { \ if (0 == strncmp(&name[offset], filter, filter_len)) { \ return 1; \ } \ } \ offset++; \ } \ \ return 0; \ } \ \ /* Before running a test, check the name filtering and \ * test shuffling state, if applicable, and then call setup hooks. */ \ int greatest_test_pre(const char *name) { \ struct greatest_run_info *g = &greatest_info; \ int match = greatest_name_match(name, g->test_filter, 1) && \ !greatest_name_match(name, g->test_exclude, 0); \ if (GREATEST_LIST_ONLY()) { /* just listing test names */ \ if (match) { fprintf(GREATEST_STDOUT, " %s\n", name); } \ return 0; \ } \ if (match && (!GREATEST_FIRST_FAIL() || g->suite.failed == 0)) { \ struct greatest_prng *p = &g->prng[1]; \ if (p->random_order) { \ p->count++; \ if (!p->initialized || ((p->count - 1) != p->state)) { \ return 0; /* don't run this test yet */ \ } \ } \ GREATEST_SET_TIME(g->suite.pre_test); \ if (g->setup) { g->setup(g->setup_udata); } \ p->count_run++; \ return 1; /* test should be run */ \ } else { \ return 0; /* skipped */ \ } \ } \ \ void greatest_test_post(const char *name, int res) { \ GREATEST_SET_TIME(greatest_info.suite.post_test); \ if (greatest_info.teardown) { \ void *udata = greatest_info.teardown_udata; \ greatest_info.teardown(udata); \ } \ \ if (res <= GREATEST_TEST_RES_FAIL) { \ greatest_do_fail(name); \ } else if (res >= GREATEST_TEST_RES_SKIP) { \ greatest_do_skip(name); \ } else if (res == GREATEST_TEST_RES_PASS) { \ greatest_do_pass(name); \ } \ greatest_info.suite.tests_run++; \ greatest_info.col++; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ greatest_info.suite.post_test); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } else if (greatest_info.col % greatest_info.width == 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ fflush(GREATEST_STDOUT); \ } \ \ static void report_suite(void) { \ if (greatest_info.suite.tests_run > 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\n%u test%s - %u passed, %u failed, %u skipped", \ greatest_info.suite.tests_run, \ greatest_info.suite.tests_run == 1 ? "" : "s", \ greatest_info.suite.passed, \ greatest_info.suite.failed, \ greatest_info.suite.skipped); \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ greatest_info.suite.post_suite); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } \ } \ \ static void update_counts_and_reset_suite(void) { \ greatest_info.setup = NULL; \ greatest_info.setup_udata = NULL; \ greatest_info.teardown = NULL; \ greatest_info.teardown_udata = NULL; \ greatest_info.passed += greatest_info.suite.passed; \ greatest_info.failed += greatest_info.suite.failed; \ greatest_info.skipped += greatest_info.suite.skipped; \ greatest_info.tests_run += greatest_info.suite.tests_run; \ memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \ greatest_info.col = 0; \ } \ \ int greatest_suite_pre(const char *suite_name) { \ struct greatest_prng *p = &greatest_info.prng[0]; \ if (!greatest_name_match(suite_name, greatest_info.suite_filter, 1) \ || (GREATEST_FIRST_FAIL() && greatest_info.failed > 0)) { \ return 0; \ } \ if (p->random_order) { \ p->count++; \ if (!p->initialized || ((p->count - 1) != p->state)) { \ return 0; /* don't run this suite yet */ \ } \ } \ p->count_run++; \ update_counts_and_reset_suite(); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ return 1; \ } \ \ void greatest_suite_post(void) { \ GREATEST_SET_TIME(greatest_info.suite.post_suite); \ report_suite(); \ } \ \ static void greatest_run_suite(greatest_suite_cb *suite_cb, \ const char *suite_name) { \ if (greatest_suite_pre(suite_name)) { \ suite_cb(); \ greatest_suite_post(); \ } \ } \ \ void greatest_do_pass(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "PASS %s: %s", \ name, greatest_info.msg ? greatest_info.msg : ""); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "."); \ } \ greatest_info.suite.passed++; \ } \ \ void greatest_do_fail(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "FAIL %s: %s (%s:%u)", \ name, greatest_info.msg ? greatest_info.msg : "", \ greatest_info.fail_file, greatest_info.fail_line); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "F"); \ greatest_info.col++; \ /* add linebreak if in line of '.'s */ \ if (greatest_info.col != 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ GREATEST_FPRINTF(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ name, \ greatest_info.msg ? greatest_info.msg : "", \ greatest_info.fail_file, greatest_info.fail_line); \ } \ greatest_info.suite.failed++; \ } \ \ void greatest_do_skip(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "SKIP %s: %s", \ name, \ greatest_info.msg ? \ greatest_info.msg : "" ); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "s"); \ } \ greatest_info.suite.skipped++; \ } \ \ int greatest_do_assert_equal_t(const void *exp, const void *got, \ greatest_type_info *type_info, void *udata) { \ int eq = 0; \ if (type_info == NULL || type_info->equal == NULL) { \ return 0; \ } \ eq = type_info->equal(exp, got, udata); \ if (!eq) { \ if (type_info->print != NULL) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ (void)type_info->print(exp, udata); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ (void)type_info->print(got, udata); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "GREATEST_ASSERT_EQUAL_T failure at %s:%u\n", \ greatest_info.fail_file, \ greatest_info.fail_line); \ } \ } \ return eq; \ } \ \ void greatest_usage(const char *name) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Usage: %s [--help] [-hlfv] [-s SUITE] [-t TEST]\n" \ " -h, --help print this Help\n" \ " -l List suites and tests, then exit (dry run)\n" \ " -f Stop runner after first failure\n" \ " -v Verbose output\n" \ " -s SUITE only run suites containing string SUITE\n" \ " -t TEST only run tests containing string TEST\n" \ " -x EXCLUDE exclude tests containing string EXCLUDE\n", \ name); \ } \ \ static void greatest_parse_options(int argc, char **argv) { \ int i = 0; \ for (i = 1; i < argc; i++) { \ if (argv[i][0] == '-') { \ char f = argv[i][1]; \ if ((f == 's' || f == 't' || f == 'x') && argc <= i + 1) { \ greatest_usage(argv[0]); exit(EXIT_FAILURE); \ } \ switch (f) { \ case 's': /* suite name filter */ \ greatest_set_suite_filter(argv[i + 1]); i++; break; \ case 't': /* test name filter */ \ greatest_set_test_filter(argv[i + 1]); i++; break; \ case 'x': /* test name exclusion */ \ greatest_set_test_exclude(argv[i + 1]); i++; break; \ case 'f': /* first fail flag */ \ greatest_stop_at_first_fail(); break; \ case 'l': /* list only (dry run) */ \ greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; break; \ case 'v': /* first fail flag */ \ greatest_info.verbosity++; break; \ case 'h': /* help */ \ greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ case '-': \ if (0 == strncmp("--help", argv[i], 6)) { \ greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ } else if (0 == strncmp("--", argv[i], 2)) { \ return; /* ignore following arguments */ \ } /* fall through */ \ default: \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Unknown argument '%s'\n", argv[i]); \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ } \ } \ } \ \ int greatest_all_passed(void) { return (greatest_info.failed == 0); } \ \ void greatest_set_test_filter(const char *filter) { \ greatest_info.test_filter = filter; \ } \ \ void greatest_set_test_exclude(const char *filter) { \ greatest_info.test_exclude = filter; \ } \ \ void greatest_set_suite_filter(const char *filter) { \ greatest_info.suite_filter = filter; \ } \ \ void greatest_stop_at_first_fail(void) { \ greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \ } \ \ void greatest_get_report(struct greatest_report_t *report) { \ if (report) { \ report->passed = greatest_info.passed; \ report->failed = greatest_info.failed; \ report->skipped = greatest_info.skipped; \ report->assertions = greatest_info.assertions; \ } \ } \ \ unsigned int greatest_get_verbosity(void) { \ return greatest_info.verbosity; \ } \ \ void greatest_set_verbosity(unsigned int verbosity) { \ greatest_info.verbosity = (unsigned char)verbosity; \ } \ \ void greatest_set_flag(greatest_flag_t flag) { \ greatest_info.flags |= flag; \ } \ \ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ greatest_info.setup = cb; \ greatest_info.setup_udata = udata; \ } \ \ void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \ void *udata) { \ greatest_info.teardown = cb; \ greatest_info.teardown_udata = udata; \ } \ \ static int greatest_string_equal_cb(const void *exp, const void *got, \ void *udata) { \ size_t *size = (size_t *)udata; \ return (size != NULL \ ? (0 == strncmp((const char *)exp, (const char *)got, *size)) \ : (0 == strcmp((const char *)exp, (const char *)got))); \ } \ \ static int greatest_string_printf_cb(const void *t, void *udata) { \ (void)udata; /* note: does not check \0 termination. */ \ return GREATEST_FPRINTF(GREATEST_STDOUT, "%s", (const char *)t); \ } \ \ greatest_type_info greatest_type_info_string = { \ greatest_string_equal_cb, \ greatest_string_printf_cb, \ }; \ \ static int greatest_memory_equal_cb(const void *exp, const void *got, \ void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ return (0 == memcmp(exp, got, env->size)); \ } \ \ /* Hexdump raw memory, with differences highlighted */ \ static int greatest_memory_printf_cb(const void *t, void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ const unsigned char *buf = (const unsigned char *)t; \ unsigned char diff_mark = ' '; \ FILE *out = GREATEST_STDOUT; \ size_t i, line_i, line_len = 0; \ int len = 0; /* format hexdump with differences highlighted */ \ for (i = 0; i < env->size; i+= line_len) { \ diff_mark = ' '; \ line_len = env->size - i; \ if (line_len > 16) { line_len = 16; } \ for (line_i = i; line_i < i + line_len; line_i++) { \ if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \ } \ len += GREATEST_FPRINTF(out, "\n%04x %c ", \ (unsigned int)i, diff_mark); \ for (line_i = i; line_i < i + line_len; line_i++) { \ int m = env->exp[line_i] == env->got[line_i]; /* match? */ \ len += GREATEST_FPRINTF(out, "%02x%c", \ buf[line_i], m ? ' ' : '<'); \ } \ for (line_i = 0; line_i < 16 - line_len; line_i++) { \ len += GREATEST_FPRINTF(out, " "); \ } \ GREATEST_FPRINTF(out, " "); \ for (line_i = i; line_i < i + line_len; line_i++) { \ unsigned char c = buf[line_i]; \ len += GREATEST_FPRINTF(out, "%c", isprint(c) ? c : '.'); \ } \ } \ len += GREATEST_FPRINTF(out, "\n"); \ return len; \ } \ \ void greatest_prng_init_first_pass(int id) { \ greatest_info.prng[id].random_order = 1; \ greatest_info.prng[id].count_run = 0; \ } \ \ int greatest_prng_init_second_pass(int id, unsigned long seed) { \ static unsigned long primes[] = { 11, 101, 1009, 10007, \ 100003, 1000003, 10000019, 100000007, 1000000007, \ 1538461, 1865471, 17471, 2147483647 /* 2**32 - 1 */, }; \ struct greatest_prng *prng = &greatest_info.prng[id]; \ if (prng->count == 0) { return 0; } \ prng->mod = 1; \ prng->count_ceil = prng->count; \ while (prng->mod < prng->count) { prng->mod <<= 1; } \ prng->state = seed & 0x1fffffff; /* only use lower 29 bits... */ \ prng->a = (4LU * prng->state) + 1; /* to avoid overflow */ \ prng->c = primes[(seed * 16451) % sizeof(primes)/sizeof(primes[0])];\ prng->initialized = 1; \ return 1; \ } \ \ /* Step the pseudorandom number generator until its state reaches \ * another test ID between 0 and the test count. \ * This use a linear congruential pseudorandom number generator, \ * with the power-of-two ceiling of the test count as the modulus, the \ * masked seed as the multiplier, and a prime as the increment. For \ * each generated value < the test count, run the corresponding test. \ * This will visit all IDs 0 <= X < mod once before repeating, \ * with a starting position chosen based on the initial seed. \ * For details, see: Knuth, The Art of Computer Programming \ * Volume. 2, section 3.2.1. */ \ void greatest_prng_step(int id) { \ struct greatest_prng *p = &greatest_info.prng[id]; \ do { \ p->state = ((p->a * p->state) + p->c) & (p->mod - 1); \ } while (p->state >= p->count_ceil); \ } \ \ greatest_type_info greatest_type_info_memory = { \ greatest_memory_equal_cb, \ greatest_memory_printf_cb, \ }; \ \ greatest_run_info greatest_info /* Init internals. */ #define GREATEST_INIT() \ do { \ /* Suppress unused function warning if features aren't used */ \ (void)greatest_run_suite; \ (void)greatest_parse_options; \ (void)greatest_prng_step; \ (void)greatest_prng_init_first_pass; \ (void)greatest_prng_init_second_pass; \ \ memset(&greatest_info, 0, sizeof(greatest_info)); \ greatest_info.width = GREATEST_DEFAULT_WIDTH; \ GREATEST_SET_TIME(greatest_info.begin); \ } while (0) \ /* Handle command-line arguments, etc. */ #define GREATEST_MAIN_BEGIN() \ do { \ GREATEST_INIT(); \ greatest_parse_options(argc, argv); \ } while (0) /* Report passes, failures, skipped tests, the number of * assertions, and the overall run time. */ #define GREATEST_PRINT_REPORT() \ do { \ if (!GREATEST_LIST_ONLY()) { \ update_counts_and_reset_suite(); \ GREATEST_SET_TIME(greatest_info.end); \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\nTotal: %u test%s", \ greatest_info.tests_run, \ greatest_info.tests_run == 1 ? "" : "s"); \ GREATEST_CLOCK_DIFF(greatest_info.begin, \ greatest_info.end); \ GREATEST_FPRINTF(GREATEST_STDOUT, ", %u assertion%s\n", \ greatest_info.assertions, \ greatest_info.assertions == 1 ? "" : "s"); \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Pass: %u, fail: %u, skip: %u.\n", \ greatest_info.passed, \ greatest_info.failed, greatest_info.skipped); \ } \ } while (0) /* Report results, exit with exit status based on results. */ #define GREATEST_MAIN_END() \ do { \ GREATEST_PRINT_REPORT(); \ return (greatest_all_passed() ? EXIT_SUCCESS : EXIT_FAILURE); \ } while (0) /* Make abbreviations without the GREATEST_ prefix for the * most commonly used symbols. */ #if GREATEST_USE_ABBREVS #define TEST GREATEST_TEST #define SUITE GREATEST_SUITE #define SUITE_EXTERN GREATEST_SUITE_EXTERN #define RUN_TEST GREATEST_RUN_TEST #define RUN_TEST1 GREATEST_RUN_TEST1 #define RUN_SUITE GREATEST_RUN_SUITE #define IGNORE_TEST GREATEST_IGNORE_TEST #define ASSERT GREATEST_ASSERT #define ASSERTm GREATEST_ASSERTm #define ASSERT_FALSE GREATEST_ASSERT_FALSE #define ASSERT_EQ GREATEST_ASSERT_EQ #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ #define ASSERT_STRN_EQ GREATEST_ASSERT_STRN_EQ #define ASSERT_MEM_EQ GREATEST_ASSERT_MEM_EQ #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm #define ASSERT_EQm GREATEST_ASSERT_EQm #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm #define ASSERT_STRN_EQm GREATEST_ASSERT_STRN_EQm #define ASSERT_MEM_EQm GREATEST_ASSERT_MEM_EQm #define ASSERT_ENUM_EQm GREATEST_ASSERT_ENUM_EQm #define PASS GREATEST_PASS #define FAIL GREATEST_FAIL #define SKIP GREATEST_SKIP #define PASSm GREATEST_PASSm #define FAILm GREATEST_FAILm #define SKIPm GREATEST_SKIPm #define SET_SETUP GREATEST_SET_SETUP_CB #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB #define CHECK_CALL GREATEST_CHECK_CALL #define SHUFFLE_TESTS GREATEST_SHUFFLE_TESTS #define SHUFFLE_SUITES GREATEST_SHUFFLE_SUITES #ifdef GREATEST_VA_ARGS #define RUN_TESTp GREATEST_RUN_TESTp #endif #if GREATEST_USE_LONGJMP #define ASSERT_OR_LONGJMP GREATEST_ASSERT_OR_LONGJMP #define ASSERT_OR_LONGJMPm GREATEST_ASSERT_OR_LONGJMPm #define FAIL_WITH_LONGJMP GREATEST_FAIL_WITH_LONGJMP #define FAIL_WITH_LONGJMPm GREATEST_FAIL_WITH_LONGJMPm #endif #endif /* USE_ABBREVS */ #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) } #endif #endif fzy-1.0/deps/greatest/package.json000066400000000000000000000004071335200710300172140ustar00rootroot00000000000000{ "name": "greatest", "version": "v1.2.1", "repo": "silentbicycle/greatest", "src": ["greatest.h"], "description": "A C testing library in 1 file. No dependencies, no dynamic allocation.", "license": "ISC", "keywords": ["test", "unit", "testing"] } fzy-1.0/deps/theft/000077500000000000000000000000001335200710300142215ustar00rootroot00000000000000fzy-1.0/deps/theft/.gitignore000066400000000000000000000000321335200710300162040ustar00rootroot00000000000000*.o test_theft libtheft.a fzy-1.0/deps/theft/LICENSE000066400000000000000000000013571335200710300152340ustar00rootroot00000000000000Copyright (c) 2014 Scott Vokes Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. fzy-1.0/deps/theft/theft.c000066400000000000000000000364321335200710300155070ustar00rootroot00000000000000#include #include "theft.h" #include "theft_types_internal.h" #include "theft_bloom.h" #include "theft_mt.h" /* Initialize a theft test runner. * BLOOM_BITS sets the size of the table used for detecting * combinations of arguments that have already been tested. * If 0, a default size will be chosen based on trial count. * (This will only be used if all property types have hash * callbacks defined.) The bloom filter can also be disabled * by setting BLOOM_BITS to THEFT_BLOOM_DISABLE. * * Returns a NULL if malloc fails or BLOOM_BITS is out of bounds. */ struct theft *theft_init(uint8_t bloom_bits) { if ((bloom_bits != 0 && (bloom_bits < THEFT_BLOOM_BITS_MIN)) || ((bloom_bits > THEFT_BLOOM_BITS_MAX) && bloom_bits != THEFT_BLOOM_DISABLE)) { return NULL; } theft *t = malloc(sizeof(*t)); if (t == NULL) { return NULL; } memset(t, 0, sizeof(*t)); t->mt = theft_mt_init(DEFAULT_THEFT_SEED); if (t->mt == NULL) { free(t); return NULL; } else { t->out = stdout; t->requested_bloom_bits = bloom_bits; return t; } } /* (Re-)initialize the random number generator with a specific seed. */ void theft_set_seed(struct theft *t, uint64_t seed) { t->seed = seed; theft_mt_reset(t->mt, seed); } /* Get a random 64-bit integer from the test runner's PRNG. */ theft_seed theft_random(struct theft *t) { theft_seed ns = (theft_seed)theft_mt_random(t->mt); return ns; } /* Get a random double from the test runner's PRNG. */ double theft_random_double(struct theft *t) { return theft_mt_random_double(t->mt); } /* Change T's output stream handle to OUT. (Default: stdout.) */ void theft_set_output_stream(struct theft *t, FILE *out) { t->out = out; } /* Check if all argument info structs have all required callbacks. */ static bool check_all_args(struct theft_propfun_info *info, bool *all_hashable) { bool ah = true; for (int i = 0; i < info->arity; i++) { struct theft_type_info *ti = info->type_info[i]; if (ti->alloc == NULL) { return false; } if (ti->hash == NULL) { ah = false; } } *all_hashable = ah; return true; } static theft_progress_callback_res default_progress_cb(struct theft_trial_info *info, void *env) { (void)info; (void)env; return THEFT_PROGRESS_CONTINUE; } static void infer_arity(struct theft_propfun_info *info) { for (int i = 0; i < THEFT_MAX_ARITY; i++) { if (info->type_info[i] == NULL) { info->arity = i; break; } } } /* Run a series of randomized trials of a property function. * * Configuration is specified in CFG; many fields are optional. * See the type definition in `theft_types.h`. */ theft_run_res theft_run(struct theft *t, struct theft_cfg *cfg) { if (t == NULL || cfg == NULL) { return THEFT_RUN_ERROR_BAD_ARGS; } struct theft_propfun_info info; memset(&info, 0, sizeof(info)); info.name = cfg->name; info.fun = cfg->fun; memcpy(info.type_info, cfg->type_info, sizeof(info.type_info)); info.always_seed_count = cfg->always_seed_count; info.always_seeds = cfg->always_seeds; if (cfg->seed) { theft_set_seed(t, cfg->seed); } else { theft_set_seed(t, DEFAULT_THEFT_SEED); } if (cfg->trials == 0) { cfg->trials = THEFT_DEF_TRIALS; } return theft_run_internal(t, &info, cfg->trials, cfg->progress_cb, cfg->env, cfg->report); } /* Actually run the trials, with all arguments made explicit. */ static theft_run_res theft_run_internal(struct theft *t, struct theft_propfun_info *info, int trials, theft_progress_cb *cb, void *env, struct theft_trial_report *r) { struct theft_trial_report fake_report; if (r == NULL) { r = &fake_report; } memset(r, 0, sizeof(*r)); infer_arity(info); if (info->arity == 0) { return THEFT_RUN_ERROR_BAD_ARGS; } if (t == NULL || info == NULL || info->fun == NULL || info->arity == 0) { return THEFT_RUN_ERROR_BAD_ARGS; } bool all_hashable = false; if (!check_all_args(info, &all_hashable)) { return THEFT_RUN_ERROR_MISSING_CALLBACK; } if (cb == NULL) { cb = default_progress_cb; } /* If all arguments are hashable, then attempt to use * a bloom filter to avoid redundant checking. */ if (all_hashable) { if (t->requested_bloom_bits == 0) { t->requested_bloom_bits = theft_bloom_recommendation(trials); } if (t->requested_bloom_bits != THEFT_BLOOM_DISABLE) { t->bloom = theft_bloom_init(t->requested_bloom_bits); } } theft_seed seed = t->seed; theft_seed initial_seed = t->seed; int always_seeds = info->always_seed_count; if (info->always_seeds == NULL) { always_seeds = 0; } void *args[THEFT_MAX_ARITY]; theft_progress_callback_res cres = THEFT_PROGRESS_CONTINUE; for (int trial = 0; trial < trials; trial++) { memset(args, 0xFF, sizeof(args)); if (cres == THEFT_PROGRESS_HALT) { break; } /* If any seeds to always run were specified, use those before * reverting to the specified starting seed. */ if (trial < always_seeds) { seed = info->always_seeds[trial]; } else if ((always_seeds > 0) && (trial == always_seeds)) { seed = initial_seed; } struct theft_trial_info ti = { .name = info->name, .trial = trial, .seed = seed, .arity = info->arity, .args = args }; theft_set_seed(t, seed); all_gen_res_t gres = gen_all_args(t, info, seed, args, env); switch (gres) { case ALL_GEN_SKIP: /* skip generating these args */ ti.status = THEFT_TRIAL_SKIP; r->skip++; cres = cb(&ti, env); break; case ALL_GEN_DUP: /* skip these args -- probably already tried */ ti.status = THEFT_TRIAL_DUP; r->dup++; cres = cb(&ti, env); break; default: case ALL_GEN_ERROR: /* Error while generating args */ ti.status = THEFT_TRIAL_ERROR; cres = cb(&ti, env); return THEFT_RUN_ERROR; case ALL_GEN_OK: /* (Extracted function to avoid deep nesting here.) */ if (!run_trial(t, info, args, cb, env, r, &ti, &cres)) { return THEFT_RUN_ERROR; } } free_args(info, args, env); /* Restore last known seed and generate next. */ theft_set_seed(t, seed); seed = theft_random(t); } if (r->fail > 0) { return THEFT_RUN_FAIL; } else { return THEFT_RUN_PASS; } } /* Now that arguments have been generated, run the trial and update * counters, call cb with results, etc. */ static bool run_trial(struct theft *t, struct theft_propfun_info *info, void **args, theft_progress_cb *cb, void *env, struct theft_trial_report *r, struct theft_trial_info *ti, theft_progress_callback_res *cres) { if (t->bloom) { mark_called(t, info, args, env); } theft_trial_res tres = call_fun(info, args); ti->status = tres; switch (tres) { case THEFT_TRIAL_PASS: r->pass++; *cres = cb(ti, env); break; case THEFT_TRIAL_FAIL: if (!attempt_to_shrink(t, info, args, env)) { ti->status = THEFT_TRIAL_ERROR; *cres = cb(ti, env); return false; } r->fail++; *cres = report_on_failure(t, info, ti, cb, env); break; case THEFT_TRIAL_SKIP: *cres = cb(ti, env); r->skip++; break; case THEFT_TRIAL_DUP: /* user callback should not return this; fall through */ case THEFT_TRIAL_ERROR: *cres = cb(ti, env); free_args(info, args, env); return false; } return true; } static void free_args(struct theft_propfun_info *info, void **args, void *env) { for (int i = 0; i < info->arity; i++) { theft_free_cb *fcb = info->type_info[i]->free; if (fcb && args[i] != THEFT_SKIP) { fcb(args[i], env); } } } void theft_free(struct theft *t) { if (t->bloom) { theft_bloom_dump(t->bloom); theft_bloom_free(t->bloom); t->bloom = NULL; } theft_mt_free(t->mt); free(t); } /* Actually call the property function. Its number of arguments is not * constrained by the typedef, but will be defined at the call site * here. (If info->arity is wrong, it will probably crash.) */ static theft_trial_res call_fun(struct theft_propfun_info *info, void **args) { theft_trial_res res = THEFT_TRIAL_ERROR; switch (info->arity) { case 1: res = info->fun(args[0]); break; case 2: res = info->fun(args[0], args[1]); break; case 3: res = info->fun(args[0], args[1], args[2]); break; case 4: res = info->fun(args[0], args[1], args[2], args[3]); break; case 5: res = info->fun(args[0], args[1], args[2], args[3], args[4]); break; case 6: res = info->fun(args[0], args[1], args[2], args[3], args[4], args[5]); break; case 7: res = info->fun(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); break; case 8: res = info->fun(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); break; case 9: res = info->fun(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); break; case 10: res = info->fun(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); break; /* ... */ default: return THEFT_TRIAL_ERROR; } return res; } /* Attempt to instantiate arguments, starting with the current seed. */ static all_gen_res_t gen_all_args(theft *t, struct theft_propfun_info *info, theft_seed seed, void *args[THEFT_MAX_ARITY], void *env) { for (int i = 0; i < info->arity; i++) { struct theft_type_info *ti = info->type_info[i]; void *p = ti->alloc(t, seed, env); if (p == THEFT_SKIP || p == THEFT_ERROR) { for (int j = 0; j < i; j++) { ti->free(args[j], env); } if (p == THEFT_SKIP) { return ALL_GEN_SKIP; } else { return ALL_GEN_ERROR; } } else { args[i] = p; } seed = theft_random(t); } /* check bloom filter */ if (t->bloom && check_called(t, info, args, env)) { return ALL_GEN_DUP; } return ALL_GEN_OK; } /* Attempt to simplify all arguments, breadth first. Continue as long as * progress is made, i.e., until a local minima is reached. */ static bool attempt_to_shrink(theft *t, struct theft_propfun_info *info, void *args[], void *env) { bool progress = false; do { progress = false; for (int ai = 0; ai < info->arity; ai++) { struct theft_type_info *ti = info->type_info[ai]; if (ti->shrink) { /* attempt to simplify this argument by one step */ shrink_res rres = attempt_to_shrink_arg(t, info, args, env, ai); switch (rres) { case SHRINK_OK: progress = true; break; case SHRINK_DEAD_END: break; default: case SHRINK_ERROR: return false; } } } } while (progress); return true; } /* Simplify an argument by trying all of its simplification tactics, in * order, and checking whether the property still fails. If it passes, * then revert the simplification and try another tactic. * * If the bloom filter is being used (i.e., if all arguments have hash * callbacks defined), then use it to skip over areas of the state * space that have probably already been tried. */ static shrink_res attempt_to_shrink_arg(theft *t, struct theft_propfun_info *info, void *args[], void *env, int ai) { struct theft_type_info *ti = info->type_info[ai]; for (uint32_t tactic = 0; tactic < THEFT_MAX_TACTICS; tactic++) { void *cur = args[ai]; void *nv = ti->shrink(cur, tactic, env); if (nv == THEFT_NO_MORE_TACTICS) { return SHRINK_DEAD_END; } else if (nv == THEFT_ERROR) { return SHRINK_ERROR; } else if (nv == THEFT_DEAD_END) { continue; /* try next tactic */ } args[ai] = nv; if (t->bloom) { if (check_called(t, info, args, env)) { /* probably redundant */ if (ti->free) { ti->free(nv, env); } args[ai] = cur; continue; } else { mark_called(t, info, args, env); } } theft_trial_res res = call_fun(info, args); switch (res) { case THEFT_TRIAL_PASS: case THEFT_TRIAL_SKIP: /* revert */ args[ai] = cur; if (ti->free) { ti->free(nv, env); } break; case THEFT_TRIAL_FAIL: if (ti->free) { ti->free(cur, env); } return SHRINK_OK; case THEFT_TRIAL_DUP: /* user callback should not return this */ case THEFT_TRIAL_ERROR: return SHRINK_ERROR; } } (void)t; return SHRINK_DEAD_END; } /* Populate a buffer with hashes of all the arguments. */ static void get_arg_hash_buffer(theft_hash *buffer, struct theft_propfun_info *info, void **args, void *env) { for (int i = 0; i < info->arity; i++) { buffer[i] = info->type_info[i]->hash(args[i], env); } } /* Mark the tuple of argument instances as called in the bloom filter. */ static void mark_called(theft *t, struct theft_propfun_info *info, void **args, void *env) { theft_hash buffer[THEFT_MAX_ARITY]; get_arg_hash_buffer(buffer, info, args, env); theft_bloom_mark(t->bloom, (uint8_t *)buffer, info->arity * sizeof(theft_hash)); } /* Check if this combination of argument instances has been called. */ static bool check_called(theft *t, struct theft_propfun_info *info, void **args, void *env) { theft_hash buffer[THEFT_MAX_ARITY]; get_arg_hash_buffer(buffer, info, args, env); return theft_bloom_check(t->bloom, (uint8_t *)buffer, info->arity * sizeof(theft_hash)); } /* Print info about a failure. */ static theft_progress_callback_res report_on_failure(theft *t, struct theft_propfun_info *info, struct theft_trial_info *ti, theft_progress_cb *cb, void *env) { static theft_progress_callback_res cres; int arity = info->arity; fprintf(t->out, "\n\n -- Counter-Example: %s\n", info->name ? info-> name : ""); fprintf(t->out, " Trial %u, Seed 0x%016llx\n", ti->trial, (uint64_t)ti->seed); for (int i = 0; i < arity; i++) { theft_print_cb *print = info->type_info[i]->print; if (print) { fprintf(t->out, " Argument %d:\n", i); print(t->out, ti->args[i], env); fprintf(t->out, "\n"); } } cres = cb(ti, env); return cres; } fzy-1.0/deps/theft/theft.h000066400000000000000000000046211335200710300155070ustar00rootroot00000000000000#ifndef THEFT_H #define THEFT_H #include #include #include #include /* Version 0.2.0 */ #define THEFT_VERSION_MAJOR 0 #define THEFT_VERSION_MINOR 2 #define THEFT_VERSION_PATCH 0 /* A property can have at most this many arguments. */ #define THEFT_MAX_ARITY 10 #include "theft_types.h" /* Default number of trials to run. */ #define THEFT_DEF_TRIALS 100 /* Min and max bits used to determine bloom filter size. * (A larger value uses more memory, but reduces the odds of an * untested argument combination being falsely skipped.) */ #define THEFT_BLOOM_BITS_MIN 13 /* 1 KB */ #define THEFT_BLOOM_BITS_MAX 33 /* 1 GB */ /* Initialize a theft test runner. * BLOOM_BITS sets the size of the table used for detecting * combinations of arguments that have already been tested. * If 0, a default size will be chosen based on trial count. * (This will only be used if all property types have hash * callbacks defined.) The bloom filter can also be disabled * by setting BLOOM_BITS to THEFT_BLOOM_DISABLE. * * Returns a NULL if malloc fails or BLOOM_BITS is out of bounds. */ struct theft *theft_init(uint8_t bloom_bits); /* Free a property-test runner. */ void theft_free(struct theft *t); /* (Re-)initialize the random number generator with a specific seed. */ void theft_set_seed(struct theft *t, uint64_t seed); /* Get a random 64-bit integer from the test runner's PRNG. */ theft_hash theft_random(struct theft *t); /* Get a random double from the test runner's PRNG. */ double theft_random_double(struct theft *t); /* Change T's output stream handle to OUT. (Default: stdout.) */ void theft_set_output_stream(struct theft *t, FILE *out); /* Run a series of randomized trials of a property function. * * Configuration is specified in CFG; many fields are optional. * See the type definition in `theft_types.h`. */ theft_run_res theft_run(struct theft *t, struct theft_cfg *cfg); /* Hash a buffer in one pass. (Wraps the below functions.) */ theft_hash theft_hash_onepass(uint8_t *data, size_t bytes); /* Init/reset a hasher for incremental hashing. * Returns true, or false if you gave it a NULL pointer. */ void theft_hash_init(struct theft_hasher *h); /* Sink more data into an incremental hash. */ void theft_hash_sink(struct theft_hasher *h, uint8_t *data, size_t bytes); /* Finish hashing and get the result. */ theft_hash theft_hash_done(struct theft_hasher *h); #endif fzy-1.0/deps/theft/theft_bloom.c000066400000000000000000000110151335200710300166650ustar00rootroot00000000000000#include #include #include "theft.h" #include "theft_bloom.h" #define DEFAULT_BLOOM_BITS 17 #define DEBUG_BLOOM_FILTER 0 #define LOG2_BITS_PER_BYTE 3 #define HASH_COUNT 4 static uint8_t get_bits_set_count(uint8_t counts[256], uint8_t byte); /* Initialize a bloom filter. */ struct theft_bloom *theft_bloom_init(uint8_t bit_size2) { size_t sz = 1 << (bit_size2 - LOG2_BITS_PER_BYTE); struct theft_bloom *b = malloc(sizeof(struct theft_bloom) + sz); if (b) { b->size = sz; b->bit_count = bit_size2; memset(b->bits, 0, sz); } return b; } /* Hash data and mark it in the bloom filter. */ void theft_bloom_mark(struct theft_bloom *b, uint8_t *data, size_t data_size) { uint64_t hash = theft_hash_onepass(data, data_size); uint8_t bc = b->bit_count; uint64_t mask = (1 << bc) - 1; /* Use HASH_COUNT distinct slices of MASK bits as hashes for the bloom filter. */ int bit_inc = (64 - bc) / HASH_COUNT; for (int i = 0; i < (64 - bc); i += bit_inc) { uint64_t v = (hash & (mask << i)) >> i; size_t offset = v / 8; uint8_t bit = 1 << (v & 0x07); b->bits[offset] |= bit; } } /* Check whether the data's hash is in the bloom filter. */ bool theft_bloom_check(struct theft_bloom *b, uint8_t *data, size_t data_size) { uint64_t hash = theft_hash_onepass(data, data_size); uint8_t bc = b->bit_count; uint64_t mask = (1 << bc) - 1; int bit_inc = (64 - bc) / HASH_COUNT; for (int i = 0; i < (64 - bc); i += bit_inc) { uint64_t v = (hash & (mask << i)) >> i; size_t offset = v / 8; uint8_t bit = 1 << (v & 0x07); if (0 == (b->bits[offset] & bit)) { return false; } } return true; } /* Free the bloom filter. */ void theft_bloom_free(struct theft_bloom *b) { free(b); } /* Dump the bloom filter's contents. (Debugging.) */ void theft_bloom_dump(struct theft_bloom *b) { uint8_t counts[256]; memset(counts, 0xFF, sizeof(counts)); size_t total = 0; uint16_t row_total = 0; for (size_t i = 0; i < b->size; i++) { uint8_t count = get_bits_set_count(counts, b->bits[i]); total += count; row_total += count; #if DEBUG_BLOOM_FILTER > 1 char c = (count == 0 ? '.' : '0' + count); printf("%c", c); if ((i & 63) == 0 || i == b->size - 1) { printf(" - %2.1f%%\n", (100 * row_total) / (64.0 * 8)); row_total = 0; } #endif } #if DEBUG_BLOOM_FILTER printf(" -- bloom filter: %zd of %zd bits set (%2d%%)\n", total, 8*b->size, (int)((100.0 * total)/(8.0*b->size))); #endif /* If the total number of bits set is > the number of bytes * in the table (i.e., > 1/8 full) then warn the user. */ if (total > b->size) { fprintf(stderr, "\nWARNING: bloom filter is %zd%% full, " "larger bloom_bits value recommended.\n", (size_t)((100 * total) / (8 * b->size))); } } /* Recommend a bloom filter size for a given number of trials. */ uint8_t theft_bloom_recommendation(int trials) { /* With a preferred priority of false positives under 0.1%, * the required number of bits m in the bloom filter is: * m = -lg(0.001)/(lg(2)^2) == 14.378 ~= 14, * so we want an array with 1 << ceil(log2(14*trials)) cells. * * Note: The above formula is for the *ideal* number of hash * functions, but we're using a hardcoded count. It appears to work * well enough in practice, though, and this can be adjusted later * without impacting the API. */ #define TRIAL_MULTIPILER 14 uint8_t res = DEFAULT_BLOOM_BITS; const uint8_t min = THEFT_BLOOM_BITS_MIN - LOG2_BITS_PER_BYTE; const uint8_t max = THEFT_BLOOM_BITS_MAX - LOG2_BITS_PER_BYTE; for (uint8_t i = min; i < max; i++) { int32_t v = (1 << i); if (v > (TRIAL_MULTIPILER * trials)) { res = i + LOG2_BITS_PER_BYTE; break; } } #if DEBUG_BLOOM_FILTER size_t sz = 1 << (res - LOG2_BITS_PER_BYTE); printf("Using %zd bytes for bloom filter: %d trials -> bit_size2 %u\n", sizeof(struct theft_bloom) + sz, trials, res); #endif return res; } /* Check a byte->bits set table, and lazily populate it. */ static uint8_t get_bits_set_count(uint8_t counts[256], uint8_t byte) { uint8_t v = counts[byte]; if (v != 0xFF) { return v; } uint8_t t = 0; for (uint8_t i = 0; i < 8; i++) { if (byte & (1 << i)) { t++; } } counts[byte] = t; return t; } fzy-1.0/deps/theft/theft_bloom.h000066400000000000000000000015241335200710300166760ustar00rootroot00000000000000#ifndef THEFT_BLOOM_H #define THEFT_BLOOM_H #include #include #include struct theft_bloom { uint8_t bit_count; size_t size; uint8_t bits[]; }; /* Initialize a bloom filter. */ struct theft_bloom *theft_bloom_init(uint8_t bit_size2); /* Hash data and mark it in the bloom filter. */ void theft_bloom_mark(struct theft_bloom *b, uint8_t *data, size_t data_size); /* Check whether the data's hash is in the bloom filter. */ bool theft_bloom_check(struct theft_bloom *b, uint8_t *data, size_t data_size); /* Free the bloom filter. */ void theft_bloom_free(struct theft_bloom *b); /* Dump the bloom filter's contents. (Debugging.) */ void theft_bloom_dump(struct theft_bloom *b); /* Recommend a bloom filter size for a given number of trials. */ uint8_t theft_bloom_recommendation(int trials); #endif fzy-1.0/deps/theft/theft_hash.c000066400000000000000000000022351335200710300165040ustar00rootroot00000000000000#include "theft.h" /* Fowler/Noll/Vo hash, 64-bit FNV-1a. * This hashing algorithm is in the public domain. * For more details, see: http://www.isthe.com/chongo/tech/comp/fnv/. */ static const uint64_t fnv64_prime = 1099511628211L; static const uint64_t fnv64_offset_basis = 14695981039346656037UL; /* Init a hasher for incremental hashing. */ void theft_hash_init(struct theft_hasher *h) { h->accum = fnv64_offset_basis; } /* Sink more data into an incremental hash. */ void theft_hash_sink(struct theft_hasher *h, uint8_t *data, size_t bytes) { if (h == NULL || data == NULL) { return; } uint64_t a = h->accum; for (size_t i = 0; i < bytes; i++) { a = (a ^ data[i]) * fnv64_prime; } h->accum = a; } /* Finish hashing and get the result. */ theft_hash theft_hash_done(struct theft_hasher *h) { theft_hash res = h->accum; theft_hash_init(h); /* reset */ return res; } /* Hash a buffer in one pass. (Wraps the above functions.) */ theft_hash theft_hash_onepass(uint8_t *data, size_t bytes) { struct theft_hasher h; theft_hash_init(&h); theft_hash_sink(&h, data, bytes); return theft_hash_done(&h); } fzy-1.0/deps/theft/theft_mt.c000066400000000000000000000115551335200710300162060ustar00rootroot00000000000000/* A C-program for MT19937-64 (2004/9/29 version). Coded by Takuji Nishimura and Makoto Matsumoto. This is a 64-bit version of Mersenne Twister pseudorandom number generator. Before using, initialize the state by using init_genrand64(seed) or init_by_array64(init_key, key_length). Copyright (C) 2004, Makoto Matsumoto and Takuji Nishimura, All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. References: T. Nishimura, ``Tables of 64-bit Mersenne Twisters'' ACM Transactions on Modeling and Computer Simulation 10. (2000) 348--357. M. Matsumoto and T. Nishimura, ``Mersenne Twister: a 623-dimensionally equidistributed uniform pseudorandom number generator'' ACM Transactions on Modeling and Computer Simulation 8. (Jan. 1998) 3--30. Any feedback is very welcome. http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces) */ /* The code has been modified to store internal state in heap/stack * allocated memory, rather than statically allocated memory, to allow * multiple instances running in the same address space. */ #include #include #include "theft_mt.h" #define NN THEFT_MT_PARAM_N #define MM 156 #define MATRIX_A 0xB5026F5AA96619E9ULL #define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */ #define LM 0x7FFFFFFFULL /* Least significant 31 bits */ static uint64_t genrand64_int64(struct theft_mt *r); /* Heap-allocate a mersenne twister struct. */ struct theft_mt *theft_mt_init(uint64_t seed) { struct theft_mt *mt = malloc(sizeof(struct theft_mt)); if (mt == NULL) { return NULL; } theft_mt_reset(mt, seed); return mt; } /* Free a heap-allocated mersenne twister struct. */ void theft_mt_free(struct theft_mt *mt) { free(mt); } /* initializes mt[NN] with a seed */ void theft_mt_reset(struct theft_mt *mt, uint64_t seed) { mt->mt[0] = seed; uint16_t mti = 0; for (mti=1; mtimt[mti] = (6364136223846793005ULL * (mt->mt[mti-1] ^ (mt->mt[mti-1] >> 62)) + mti); } mt->mti = mti; } /* Get a 64-bit random number. */ uint64_t theft_mt_random(struct theft_mt *mt) { return genrand64_int64(mt); } /* Generate a random number on [0,1]-real-interval. */ double theft_mt_random_double(struct theft_mt *mt) { return (genrand64_int64(mt) >> 11) * (1.0/9007199254740991.0); } /* generates a random number on [0, 2^64-1]-interval */ static uint64_t genrand64_int64(struct theft_mt *r) { int i; uint64_t x; static uint64_t mag01[2]={0ULL, MATRIX_A}; if (r->mti >= NN) { /* generate NN words at one time */ /* if init has not been called, */ /* a default initial seed is used */ if (r->mti == NN+1) theft_mt_reset(r, 5489ULL); for (i=0;imt[i]&UM)|(r->mt[i+1]&LM); r->mt[i] = r->mt[i+MM] ^ (x>>1) ^ mag01[(int)(x&1ULL)]; } for (;imt[i]&UM)|(r->mt[i+1]&LM); r->mt[i] = r->mt[i+(MM-NN)] ^ (x>>1) ^ mag01[(int)(x&1ULL)]; } x = (r->mt[NN-1]&UM)|(r->mt[0]&LM); r->mt[NN-1] = r->mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)]; r->mti = 0; } x = r->mt[r->mti++]; x ^= (x >> 29) & 0x5555555555555555ULL; x ^= (x << 17) & 0x71D67FFFEDA60000ULL; x ^= (x << 37) & 0xFFF7EEE000000000ULL; x ^= (x >> 43); return x; } fzy-1.0/deps/theft/theft_mt.h000066400000000000000000000020611335200710300162030ustar00rootroot00000000000000#ifndef THEFT_MT_H #define THEFT_MT_H #include /* Wrapper for Mersenne Twister. * See copyright and license in theft_mt.c, more details at: * http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html * * The code has been modified to store internal state in heap/stack * allocated memory, rather than statically allocated memory, to allow * multiple instances running in the same address space. */ #define THEFT_MT_PARAM_N 312 struct theft_mt { uint64_t mt[THEFT_MT_PARAM_N]; /* the array for the state vector */ int16_t mti; }; /* Heap-allocate a mersenne twister struct. */ struct theft_mt *theft_mt_init(uint64_t seed); /* Free a heap-allocated mersenne twister struct. */ void theft_mt_free(struct theft_mt *mt); /* Reset a mersenne twister struct, possibly stack-allocated. */ void theft_mt_reset(struct theft_mt *mt, uint64_t seed); /* Get a 64-bit random number. */ uint64_t theft_mt_random(struct theft_mt *mt); /* Generate a random number on [0,1]-real-interval. */ double theft_mt_random_double(struct theft_mt *mt); #endif fzy-1.0/deps/theft/theft_types.h000066400000000000000000000160641335200710300167370ustar00rootroot00000000000000#ifndef THEFT_TYPES_H #define THEFT_TYPES_H /* A pseudo-random number/seed, used to generate instances. */ typedef uint64_t theft_seed; /* A hash of an instance. */ typedef uint64_t theft_hash; /* These are opaque, as far as the API is concerned. */ struct theft_bloom; /* bloom filter */ struct theft_mt; /* mersenne twister PRNG */ /* Struct for property-testing state. */ struct theft { FILE *out; theft_seed seed; uint8_t requested_bloom_bits; struct theft_bloom *bloom; /* bloom filter */ struct theft_mt *mt; /* random number generator */ }; /* Special sentinel values returned instead of instance pointers. */ #define THEFT_SKIP ((void *)-1) #define THEFT_ERROR ((void *)-2) #define THEFT_DEAD_END ((void *)-1) #define THEFT_NO_MORE_TACTICS ((void *)-3) /* Explicitly disable using the bloom filter. * Note that if you do this, you must be sure your simplify function * *always* returns a simpler value, or it will loop forever. */ #define THEFT_BLOOM_DISABLE ((uint8_t)-1) /* Allocate and return an instance of the type, based on a known * pseudo-random number seed. To get additional seeds, use * theft_random(t); this stream of numbers will be deterministic, so if * the alloc callback is constructed appropriately, an identical * instance can be constructed later from the same initial seed. * * Returns a pointer to the instance, THEFT_ERROR, or THEFT_SKIP. */ typedef void *(theft_alloc_cb)(struct theft *t, theft_seed seed, void *env); /* Free an instance. */ typedef void (theft_free_cb)(void *instance, void *env); /* Hash an instance. Used to skip combinations of arguments which * have probably already been checked. */ typedef theft_hash (theft_hash_cb)(void *instance, void *env); /* Attempt to shrink an instance to a simpler instance. * * For a given INSTANCE, there are likely to be multiple ways in which * it can be simplified. For example, a list of unsigned ints could have * the first element decremented, divided by 2, or dropped. This * callback should return a pointer to a freshly allocated, simplified * instance, or should return THEFT_DEAD_END to indicate that the * instance cannot be simplified further by this method. * * These tactics will be lazily explored breadth-first, to * try to find simpler versions of arguments that cause the * property to no longer hold. * * If this callback is NULL, it is equivalent to always returning * THEFT_NO_MORE_TACTICS. */ typedef void *(theft_shrink_cb)(void *instance, uint32_t tactic, void *env); /* Print INSTANCE to output stream F. * Used for displaying counter-examples. Can be NULL. */ typedef void (theft_print_cb)(FILE *f, void *instance, void *env); /* Result from a single trial. */ typedef enum { THEFT_TRIAL_PASS, /* property held */ THEFT_TRIAL_FAIL, /* property contradicted */ THEFT_TRIAL_SKIP, /* user requested skip; N/A */ THEFT_TRIAL_DUP, /* args probably already tried */ THEFT_TRIAL_ERROR, /* unrecoverable error, halt */ } theft_trial_res; /* A test property function. Arguments must match the types specified by * theft_cfg.type_info, or the result will be undefined. For example, a * propfun `prop_foo(A x, B y, C z)` must have a type_info array of * `{ info_A, info_B, info_C }`. * * Should return: * THEFT_TRIAL_PASS if the property holds, * THEFT_TRIAL_FAIL if a counter-example is found, * THEFT_TRIAL_SKIP if the combination of args isn't applicable, * or THEFT_TRIAL_ERROR if the whole run should be halted. */ typedef theft_trial_res (theft_propfun)( /* arguments unconstrained */ ); /* Callbacks used for testing with random instances of a type. * For more information, see comments on their typedefs. */ struct theft_type_info { /* Required: */ theft_alloc_cb *alloc; /* gen random instance from seed */ /* Optional, but recommended: */ theft_free_cb *free; /* free instance */ theft_hash_cb *hash; /* instance -> hash */ theft_shrink_cb *shrink; /* shrink instance */ theft_print_cb *print; /* fprintf instance */ }; /* Result from an individual trial. */ struct theft_trial_info { const char *name; /* property name */ int trial; /* N'th trial */ theft_seed seed; /* Seed used */ theft_trial_res status; /* Run status */ uint8_t arity; /* Number of arguments */ void **args; /* Arguments used */ }; /* Whether to keep running trials after N failures/skips/etc. */ typedef enum { THEFT_PROGRESS_CONTINUE, /* keep running trials */ THEFT_PROGRESS_HALT, /* no need to continue */ } theft_progress_callback_res; /* Handle test results. * Can be used to halt after too many failures, print '.' after * every N trials, etc. */ typedef theft_progress_callback_res (theft_progress_cb)(struct theft_trial_info *info, void *env); /* Result from a trial run. */ typedef enum { THEFT_RUN_PASS = 0, /* no failures */ THEFT_RUN_FAIL = 1, /* 1 or more failures */ THEFT_RUN_ERROR = 2, /* an error occurred */ THEFT_RUN_ERROR_BAD_ARGS = -1, /* API misuse */ /* Missing required callback for 1 or more types */ THEFT_RUN_ERROR_MISSING_CALLBACK = -2, } theft_run_res; /* Optional report from a trial run; same meanings as theft_trial_res. */ struct theft_trial_report { size_t pass; size_t fail; size_t skip; size_t dup; }; /* Configuration struct for a theft test. * In C99, this struct can be specified as a literal, like this: * * struct theft_cfg cfg = { * .name = "example", * .fun = prop_fun, * .type_info = { type_arg_a, type_arg_b }, * .seed = 0x7he5eed, * }; * * and omitted fields will be set to defaults. * */ struct theft_cfg { /* Property function under test, and info about its arguments. * The function is called with as many arguments are there * are values in TYPE_INFO, so it can crash if that is wrong. */ theft_propfun *fun; struct theft_type_info *type_info[THEFT_MAX_ARITY]; /* -- All fields after this point are optional. -- */ /* Property name, displayed in test runner output. */ const char *name; /* Array of seeds to always run, and its length. * Can be used for regression tests. */ int always_seed_count; /* number of seeds */ theft_seed *always_seeds; /* seeds to always run */ /* Number of trials to run. Defaults to THEFT_DEF_TRIALS. */ int trials; /* Progress callback, used to display progress in * slow-running tests, halt early under certain conditions, etc. */ theft_progress_cb *progress_cb; /* Environment pointer. This is completely opaque to theft itself, * but will be passed along to all callbacks. */ void *env; /* Struct to populate with more detailed test results. */ struct theft_trial_report *report; /* Seed for the random number generator. */ theft_seed seed; }; /* Internal state for incremental hashing. */ struct theft_hasher { theft_hash accum; }; #endif fzy-1.0/deps/theft/theft_types_internal.h000066400000000000000000000050331335200710300206250ustar00rootroot00000000000000#ifndef THEFT_TYPES_INTERNAL_H #define THEFT_TYPES_INTERNAL_H typedef struct theft theft; #define THEFT_MAX_TACTICS ((uint32_t)-1) #define DEFAULT_THEFT_SEED 0xa600d16b175eedL typedef enum { ALL_GEN_OK, /* all arguments generated okay */ ALL_GEN_SKIP, /* skip due to user constraints */ ALL_GEN_DUP, /* skip probably duplicated trial */ ALL_GEN_ERROR, /* memory error or other failure */ } all_gen_res_t; typedef enum { SHRINK_OK, /* simplified argument further */ SHRINK_DEAD_END, /* at local minima */ SHRINK_ERROR, /* hard error during shrinking */ } shrink_res; /* Testing context for a specific property function. */ struct theft_propfun_info { const char *name; /* property name, can be NULL */ theft_propfun *fun; /* property function under test */ /* Type info for ARITY arguments. */ uint8_t arity; /* number of arguments */ struct theft_type_info *type_info[THEFT_MAX_ARITY]; /* Optional array of seeds to always run. * Can be used for regression tests. */ int always_seed_count; /* number of seeds */ theft_seed *always_seeds; /* seeds to always run */ }; static theft_trial_res call_fun(struct theft_propfun_info *info, void **args); static bool run_trial(struct theft *t, struct theft_propfun_info *info, void **args, theft_progress_cb *cb, void *env, struct theft_trial_report *r, struct theft_trial_info *ti, theft_progress_callback_res *cres); static void mark_called(theft *t, struct theft_propfun_info *info, void **args, void *env); static bool check_called(theft *t, struct theft_propfun_info *info, void **args, void *env); static theft_progress_callback_res report_on_failure(theft *t, struct theft_propfun_info *info, struct theft_trial_info *ti, theft_progress_cb *cb, void *env); static all_gen_res_t gen_all_args(theft *t, struct theft_propfun_info *info, theft_seed seed, void *args[THEFT_MAX_ARITY], void *env); static void free_args(struct theft_propfun_info *info, void **args, void *env); static theft_run_res theft_run_internal(struct theft *t, struct theft_propfun_info *info, int trials, theft_progress_cb *cb, void *env, struct theft_trial_report *r); static bool attempt_to_shrink(theft *t, struct theft_propfun_info *info, void *args[], void *env); static shrink_res attempt_to_shrink_arg(theft *t, struct theft_propfun_info *info, void *args[], void *env, int ai); #endif fzy-1.0/fzy.1000066400000000000000000000043531335200710300130530ustar00rootroot00000000000000.TH FZY 1 "2018-09-23" "fzy 1.0" .SH NAME fzy \- A fuzzy text selector menu for the terminal. .SH SYNOPSIS .B fzy .IR [OPTION]... .SH DESCRIPTION .B fzy is a fuzzy text selector/file finder for the terminal using a search similar to that of TextMate or CmdT. fzy reads a list of newline-separated items from stdin to be displayed as a menu in the terminal. Upon pressing ENTER, the currently selected item is printed to stdout. Entering text narrows the items using fuzzy matching. Results are sorted using heuristics for the best match. .SH OPTIONS .TP .BR \-l ", " \-\-lines =\fILINES\fR How many lines of items to display. If unspecified, defaults to 10 lines. . .TP .BR \-p ", " \-\-prompt =\fIPROMPT\fR Input prompt (default: '> ') . .TP .BR \-s ", " \-\-show-scores Show the scores for each item. . .TP .BR \-t ", " \-\-tty =\fITTY\fR Use TTY instead of the default tty device (/dev/tty). . .TP .BR \-q ", " \-\-query =\fIQUERY\fR Use QUERY as the initial search query. . .TP .BR \-e ", " \-\-show-matches =\fIQUERY\fR Non-interactive mode. Print the matches in sorted order for QUERY to stdout. . .TP .BR \-h ", " \-\-help Usage help. . .TP .BR \-v ", " \-\-version Usage help. . .SH KEYS . .TP .BR "ENTER" Print the selected item to stdout and exit .TP .BR "Ctrl+c, Esc" Exit with status 1, without making a selection. .TP .BR "Up Arrow, Ctrl+p" Select the previous item .TP .BR "Down Arrow, Ctrl+n" Select the next item .TP Tab Replace the current search string with the selected item .TP .BR "Backspace, Ctrl+h" Delete the character before the cursor .TP .BR Ctrl+w Delete the word before the cursor .TP .BR Ctrl+u Delete the entire line . .SH USAGE EXAMPLES . .TP .BR "ls | fzy" Present a menu of items in the current directory .TP .BR "ls | fzy -l 25" Same as above, but show 25 lines of items .TP .BR "vi $(find -type f | fzy)" List files under the current directory and open the one selected in vi. .TP .BR "cd $(find -type d | fzy)" Present all directories under current path, and change to the one selected. .TP .BR "ps aux | fzy | awk '{ print $2 }' | xargs kill" List running processes, kill the selected process .TP .BR "git checkout $(git branch | cut -c 3- | fzy)" Same as above, but switching git branches. .SH AUTHOR John Hawthorn fzy-1.0/src/000077500000000000000000000000001335200710300127435ustar00rootroot00000000000000fzy-1.0/src/bonus.h000066400000000000000000000034611335200710300142460ustar00rootroot00000000000000#ifndef BONUS_H #define BONUS_H BONUS_H #include "../config.h" #define ASSIGN_LOWER(v) \ ['a'] = (v), \ ['b'] = (v), \ ['c'] = (v), \ ['d'] = (v), \ ['e'] = (v), \ ['f'] = (v), \ ['g'] = (v), \ ['h'] = (v), \ ['i'] = (v), \ ['j'] = (v), \ ['k'] = (v), \ ['l'] = (v), \ ['m'] = (v), \ ['n'] = (v), \ ['o'] = (v), \ ['p'] = (v), \ ['q'] = (v), \ ['r'] = (v), \ ['s'] = (v), \ ['t'] = (v), \ ['u'] = (v), \ ['v'] = (v), \ ['w'] = (v), \ ['x'] = (v), \ ['y'] = (v), \ ['z'] = (v) #define ASSIGN_UPPER(v) \ ['A'] = (v), \ ['B'] = (v), \ ['C'] = (v), \ ['D'] = (v), \ ['E'] = (v), \ ['F'] = (v), \ ['G'] = (v), \ ['H'] = (v), \ ['I'] = (v), \ ['J'] = (v), \ ['K'] = (v), \ ['L'] = (v), \ ['M'] = (v), \ ['N'] = (v), \ ['O'] = (v), \ ['P'] = (v), \ ['Q'] = (v), \ ['R'] = (v), \ ['S'] = (v), \ ['T'] = (v), \ ['U'] = (v), \ ['V'] = (v), \ ['W'] = (v), \ ['X'] = (v), \ ['Y'] = (v), \ ['Z'] = (v) #define ASSIGN_DIGIT(v) \ ['0'] = (v), \ ['1'] = (v), \ ['2'] = (v), \ ['3'] = (v), \ ['4'] = (v), \ ['5'] = (v), \ ['6'] = (v), \ ['7'] = (v), \ ['8'] = (v), \ ['9'] = (v) const score_t bonus_states[3][256] = { { 0 }, { ['/'] = SCORE_MATCH_SLASH, ['-'] = SCORE_MATCH_WORD, ['_'] = SCORE_MATCH_WORD, [' '] = SCORE_MATCH_WORD, ['.'] = SCORE_MATCH_DOT, }, { ['/'] = SCORE_MATCH_SLASH, ['-'] = SCORE_MATCH_WORD, ['_'] = SCORE_MATCH_WORD, [' '] = SCORE_MATCH_WORD, ['.'] = SCORE_MATCH_DOT, /* ['a' ... 'z'] = SCORE_MATCH_CAPITAL, */ ASSIGN_LOWER(SCORE_MATCH_CAPITAL) } }; const size_t bonus_index[256] = { /* ['A' ... 'Z'] = 2 */ ASSIGN_UPPER(2), /* ['a' ... 'z'] = 1 */ ASSIGN_LOWER(1), /* ['0' ... '9'] = 1 */ ASSIGN_DIGIT(1) }; #define COMPUTE_BONUS(last_ch, ch) (bonus_states[bonus_index[(unsigned char)(ch)]][(unsigned char)(last_ch)]) #endif fzy-1.0/src/choices.c000066400000000000000000000167671335200710300145450ustar00rootroot00000000000000#include #include #include #include #include #include #include "options.h" #include "choices.h" #include "match.h" /* Initial size of buffer for storing input in memory */ #define INITIAL_BUFFER_CAPACITY 4096 /* Initial size of choices array */ #define INITIAL_CHOICE_CAPACITY 128 static int cmpchoice(const void *_idx1, const void *_idx2) { const struct scored_result *a = _idx1; const struct scored_result *b = _idx2; if (a->score == b->score) { /* To ensure a stable sort, we must also sort by the string * pointers. We can do this since we know all the strings are * from a contiguous memory segment (buffer in choices_t). */ if (a->str < b->str) { return -1; } else { return 1; } } else if (a->score < b->score) { return 1; } else { return -1; } } static void *safe_realloc(void *buffer, size_t size) { buffer = realloc(buffer, size); if (!buffer) { fprintf(stderr, "Error: Can't allocate memory (%zu bytes)\n", size); abort(); } return buffer; } void choices_fread(choices_t *c, FILE *file) { /* Save current position for parsing later */ size_t buffer_start = c->buffer_size; /* Resize buffer to at least one byte more capacity than our current * size. This uses a power of two of INITIAL_BUFFER_CAPACITY. * This must work even when c->buffer is NULL and c->buffer_size is 0 */ size_t capacity = INITIAL_BUFFER_CAPACITY; while (capacity <= c->buffer_size) capacity *= 2; c->buffer = safe_realloc(c->buffer, capacity); /* Continue reading until we get a "short" read, indicating EOF */ while ((c->buffer_size += fread(c->buffer + c->buffer_size, 1, capacity - c->buffer_size, file)) == capacity) { capacity *= 2; c->buffer = safe_realloc(c->buffer, capacity); } c->buffer = safe_realloc(c->buffer, c->buffer_size + 1); c->buffer[c->buffer_size++] = '\0'; /* Truncate buffer to used size, (maybe) freeing some memory for * future allocations. */ /* Tokenize input and add to choices */ char *line = c->buffer + buffer_start; do { char *nl = strchr(line, '\n'); if (nl) *nl++ = '\0'; /* Skip empty lines */ if (*line) choices_add(c, line); line = nl; } while (line); } static void choices_resize(choices_t *c, size_t new_capacity) { c->strings = safe_realloc(c->strings, new_capacity * sizeof(const char *)); c->capacity = new_capacity; } static void choices_reset_search(choices_t *c) { free(c->results); c->selection = c->available = 0; c->results = NULL; } void choices_init(choices_t *c, options_t *options) { c->strings = NULL; c->results = NULL; c->buffer_size = 0; c->buffer = NULL; c->capacity = c->size = 0; choices_resize(c, INITIAL_CHOICE_CAPACITY); if (options->workers) { c->worker_count = options->workers; } else { c->worker_count = (int)sysconf(_SC_NPROCESSORS_ONLN); } choices_reset_search(c); } void choices_destroy(choices_t *c) { free(c->buffer); c->buffer = NULL; c->buffer_size = 0; free(c->strings); c->strings = NULL; c->capacity = c->size = 0; free(c->results); c->results = NULL; c->available = c->selection = 0; } void choices_add(choices_t *c, const char *choice) { /* Previous search is now invalid */ choices_reset_search(c); if (c->size == c->capacity) { choices_resize(c, c->capacity * 2); } c->strings[c->size++] = choice; } size_t choices_available(choices_t *c) { return c->available; } #define BATCH_SIZE 512 struct result_list { struct scored_result *list; size_t size; }; struct search_job { pthread_mutex_t lock; choices_t *choices; const char *search; size_t processed; struct worker *workers; }; struct worker { pthread_t thread_id; struct search_job *job; unsigned int worker_num; struct result_list result; }; static void worker_get_next_batch(struct search_job *job, size_t *start, size_t *end) { pthread_mutex_lock(&job->lock); *start = job->processed; job->processed += BATCH_SIZE; if (job->processed > job->choices->size) { job->processed = job->choices->size; } *end = job->processed; pthread_mutex_unlock(&job->lock); } static struct result_list merge2(struct result_list list1, struct result_list list2) { size_t result_index = 0, index1 = 0, index2 = 0; struct result_list result; result.size = list1.size + list2.size; result.list = malloc(result.size * sizeof(struct scored_result)); if (!result.list) { fprintf(stderr, "Error: Can't allocate memory\n"); abort(); } while(index1 < list1.size && index2 < list2.size) { if (cmpchoice(&list1.list[index1], &list2.list[index2]) < 0) { result.list[result_index++] = list1.list[index1++]; } else { result.list[result_index++] = list2.list[index2++]; } } while(index1 < list1.size) { result.list[result_index++] = list1.list[index1++]; } while(index2 < list2.size) { result.list[result_index++] = list2.list[index2++]; } free(list1.list); free(list2.list); return result; } static void *choices_search_worker(void *data) { struct worker *w = (struct worker *)data; struct search_job *job = w->job; const choices_t *c = job->choices; struct result_list *result = &w->result; size_t start, end; for(;;) { worker_get_next_batch(job, &start, &end); if(start == end) { break; } for(size_t i = start; i < end; i++) { if (has_match(job->search, c->strings[i])) { result->list[result->size].str = c->strings[i]; result->list[result->size].score = match(job->search, c->strings[i]); result->size++; } } } /* Sort the partial result */ qsort(result->list, result->size, sizeof(struct scored_result), cmpchoice); /* Fan-in, merging results */ for(unsigned int step = 0;; step++) { if (w->worker_num % (2 << step)) break; unsigned int next_worker = w->worker_num | (1 << step); if (next_worker >= c->worker_count) break; if ((errno = pthread_join(job->workers[next_worker].thread_id, NULL))) { perror("pthread_join"); exit(EXIT_FAILURE); } w->result = merge2(w->result, job->workers[next_worker].result); } return NULL; } void choices_search(choices_t *c, const char *search) { choices_reset_search(c); struct search_job *job = calloc(1, sizeof(struct search_job)); job->search = search; job->choices = c; if (pthread_mutex_init(&job->lock, NULL) != 0) { fprintf(stderr, "Error: pthread_mutex_init failed\n"); abort(); } job->workers = calloc(c->worker_count, sizeof(struct worker)); struct worker *workers = job->workers; for (int i = c->worker_count - 1; i >= 0; i--) { workers[i].job = job; workers[i].worker_num = i; workers[i].result.size = 0; workers[i].result.list = malloc(c->size * sizeof(struct scored_result)); /* FIXME: This is overkill */ /* These must be created last-to-first to avoid a race condition when fanning in */ if ((errno = pthread_create(&workers[i].thread_id, NULL, &choices_search_worker, &workers[i]))) { perror("pthread_create"); exit(EXIT_FAILURE); } } if (pthread_join(workers[0].thread_id, NULL)) { perror("pthread_join"); exit(EXIT_FAILURE); } c->results = workers[0].result.list; c->available = workers[0].result.size; free(workers); pthread_mutex_destroy(&job->lock); free(job); } const char *choices_get(choices_t *c, size_t n) { if (n < c->available) { return c->results[n].str; } else { return NULL; } } score_t choices_getscore(choices_t *c, size_t n) { return c->results[n].score; } void choices_prev(choices_t *c) { if (c->available) c->selection = (c->selection + c->available - 1) % c->available; } void choices_next(choices_t *c) { if (c->available) c->selection = (c->selection + 1) % c->available; } fzy-1.0/src/choices.h000066400000000000000000000015201335200710300145270ustar00rootroot00000000000000#ifndef CHOICES_H #define CHOICES_H CHOICES_H #include #include "match.h" #include "options.h" struct scored_result { score_t score; const char *str; }; typedef struct { char *buffer; size_t buffer_size; size_t capacity; size_t size; const char **strings; struct scored_result *results; size_t available; size_t selection; unsigned int worker_count; } choices_t; void choices_init(choices_t *c, options_t *options); void choices_fread(choices_t *c, FILE *file); void choices_destroy(choices_t *c); void choices_add(choices_t *c, const char *choice); size_t choices_available(choices_t *c); void choices_search(choices_t *c, const char *search); const char *choices_get(choices_t *c, size_t n); score_t choices_getscore(choices_t *c, size_t n); void choices_prev(choices_t *c); void choices_next(choices_t *c); #endif fzy-1.0/src/config.def.h000066400000000000000000000006071335200710300151210ustar00rootroot00000000000000#define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW #define SCORE_GAP_LEADING -0.005 #define SCORE_GAP_TRAILING -0.005 #define SCORE_GAP_INNER -0.01 #define SCORE_MATCH_CONSECUTIVE 1.0 #define SCORE_MATCH_SLASH 0.9 #define SCORE_MATCH_WORD 0.8 #define SCORE_MATCH_CAPITAL 0.7 #define SCORE_MATCH_DOT 0.6 /* Time (in ms) to wait for additional bytes of an escape sequence */ #define KEYTIMEOUT 25 fzy-1.0/src/fzy.c000066400000000000000000000030371335200710300137220ustar00rootroot00000000000000#include #include #include #include #include #include #include "match.h" #include "tty.h" #include "choices.h" #include "options.h" #include "tty_interface.h" #include "../config.h" int main(int argc, char *argv[]) { int ret = 0; options_t options; options_parse(&options, argc, argv); choices_t choices; choices_init(&choices, &options); if (options.benchmark) { if (!options.filter) { fprintf(stderr, "Must specify -e/--show-matches with --benchmark\n"); exit(EXIT_FAILURE); } choices_fread(&choices, stdin); for (int i = 0; i < options.benchmark; i++) choices_search(&choices, options.filter); } else if (options.filter) { choices_fread(&choices, stdin); choices_search(&choices, options.filter); for (size_t i = 0; i < choices_available(&choices); i++) { if (options.show_scores) printf("%f\t", choices_getscore(&choices, i)); printf("%s\n", choices_get(&choices, i)); } } else { /* interactive */ if (isatty(STDIN_FILENO)) choices_fread(&choices, stdin); tty_t tty; tty_init(&tty, options.tty_filename); if (!isatty(STDIN_FILENO)) choices_fread(&choices, stdin); if (options.num_lines > choices.size) options.num_lines = choices.size; if (options.num_lines + 1 > tty_getheight(&tty)) options.num_lines = tty_getheight(&tty) - 1; tty_interface_t tty_interface; tty_interface_init(&tty_interface, &tty, &choices, &options); ret = tty_interface_run(&tty_interface); } choices_destroy(&choices); return ret; } fzy-1.0/src/match.c000066400000000000000000000102441335200710300142040ustar00rootroot00000000000000#include #include #include #include #include #include #include "match.h" #include "bonus.h" #include "../config.h" char *strcasechr(const char *s, char c) { const char accept[3] = {c, toupper(c), 0}; return strpbrk(s, accept); } int has_match(const char *needle, const char *haystack) { while (*needle) { char nch = *needle++; if (!(haystack = strcasechr(haystack, nch))) { return 0; } haystack++; } return 1; } #define max(a, b) (((a) > (b)) ? (a) : (b)) #ifdef DEBUG_VERBOSE /* print one of the internal matrices */ void mat_print(score_t *mat, char name, const char *needle, const char *haystack) { int n = strlen(needle); int m = strlen(haystack); int i, j; fprintf(stderr, "%c ", name); for (j = 0; j < m; j++) { fprintf(stderr, " %c", haystack[j]); } fprintf(stderr, "\n"); for (i = 0; i < n; i++) { fprintf(stderr, " %c |", needle[i]); for (j = 0; j < m; j++) { score_t val = mat[i * m + j]; if (val == SCORE_MIN) { fprintf(stderr, " -\u221E"); } else { fprintf(stderr, " %.3f", val); } } fprintf(stderr, "\n"); } fprintf(stderr, "\n\n"); } #endif static void precompute_bonus(const char *haystack, score_t *match_bonus) { /* Which positions are beginning of words */ int m = strlen(haystack); char last_ch = '/'; for (int i = 0; i < m; i++) { char ch = haystack[i]; match_bonus[i] = COMPUTE_BONUS(last_ch, ch); last_ch = ch; } } score_t match_positions(const char *needle, const char *haystack, size_t *positions) { if (!*needle) return SCORE_MIN; int n = strlen(needle); int m = strlen(haystack); if (n == m) { /* Since this method can only be called with a haystack which * matches needle. If the lengths of the strings are equal the * strings themselves must also be equal (ignoring case). */ if (positions) for (int i = 0; i < n; i++) positions[i] = i; return SCORE_MAX; } if (m > 1024) { /* * Unreasonably large candidate: return no score * If it is a valid match it will still be returned, it will * just be ranked below any reasonably sized candidates */ return SCORE_MIN; } score_t match_bonus[m]; score_t D[n][m], M[n][m]; /* * D[][] Stores the best score for this position ending with a match. * M[][] Stores the best possible score at this position. */ precompute_bonus(haystack, match_bonus); for (int i = 0; i < n; i++) { score_t prev_score = SCORE_MIN; score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER; for (int j = 0; j < m; j++) { if (tolower(needle[i]) == tolower(haystack[j])) { score_t score = SCORE_MIN; if (!i) { score = (j * SCORE_GAP_LEADING) + match_bonus[j]; } else if (j) { /* i > 0 && j > 0*/ score = max( M[i - 1][j - 1] + match_bonus[j], /* consecutive match, doesn't stack with match_bonus */ D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE); } D[i][j] = score; M[i][j] = prev_score = max(score, prev_score + gap_score); } else { D[i][j] = SCORE_MIN; M[i][j] = prev_score = prev_score + gap_score; } } } #ifdef DEBUG_VERBOSE fprintf(stderr, "\"%s\" =~ \"%s\"\n", needle, haystack); mat_print(&D[0][0], 'D', needle, haystack); mat_print(&M[0][0], 'M', needle, haystack); fprintf(stderr, "\n"); #endif /* backtrace to find the positions of optimal matching */ if (positions) { int match_required = 0; for (int i = n - 1, j = m - 1; i >= 0; i--) { for (; j >= 0; j--) { /* * There may be multiple paths which result in * the optimal weight. * * For simplicity, we will pick the first one * we encounter, the latest in the candidate * string. */ if (D[i][j] != SCORE_MIN && (match_required || D[i][j] == M[i][j])) { /* If this score was determined using * SCORE_MATCH_CONSECUTIVE, the * previous character MUST be a match */ match_required = i && j && M[i][j] == D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE; positions[i] = j--; break; } } } } return M[n - 1][m - 1]; } score_t match(const char *needle, const char *haystack) { return match_positions(needle, haystack, NULL); } fzy-1.0/src/match.h000066400000000000000000000005341335200710300142120ustar00rootroot00000000000000#ifndef MATCH_H #define MATCH_H MATCH_H #include typedef double score_t; #define SCORE_MAX INFINITY #define SCORE_MIN -INFINITY int has_match(const char *needle, const char *haystack); score_t match_positions(const char *needle, const char *haystack, size_t *positions); score_t match(const char *needle, const char *haystack); #endif fzy-1.0/src/options.c000066400000000000000000000061421335200710300146050ustar00rootroot00000000000000#include #include #include #include #include #include "options.h" static const char *usage_str = "" "Usage: fzy [OPTION]...\n" " -l, --lines=LINES Specify how many lines of results to show (default 10)\n" " -p, --prompt=PROMPT Input prompt (default '> ')\n" " -q, --query=QUERY Use QUERY as the initial search string\n" " -e, --show-matches=QUERY Output the sorted matches of QUERY\n" " -t, --tty=TTY Specify file to use as TTY device (default /dev/tty)\n" " -s, --show-scores Show the scores of each match\n" " -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)\n" " -h, --help Display this help and exit\n" " -v, --version Output version information and exit\n"; static void usage(const char *argv0) { fprintf(stderr, usage_str, argv0); } static struct option longopts[] = {{"show-matches", required_argument, NULL, 'e'}, {"query", required_argument, NULL, 'q'}, {"lines", required_argument, NULL, 'l'}, {"tty", required_argument, NULL, 't'}, {"prompt", required_argument, NULL, 'p'}, {"show-scores", no_argument, NULL, 's'}, {"version", no_argument, NULL, 'v'}, {"benchmark", optional_argument, NULL, 'b'}, {"workers", required_argument, NULL, 'j'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; void options_init(options_t *options) { /* set defaults */ options->benchmark = 0; options->filter = NULL; options->init_search = NULL; options->tty_filename = "/dev/tty"; options->show_scores = 0; options->num_lines = 10; options->scrolloff = 1; options->prompt = "> "; options->workers = 0; } void options_parse(options_t *options, int argc, char *argv[]) { options_init(options); int c; while ((c = getopt_long(argc, argv, "vhse:q:l:t:p:j:", longopts, NULL)) != -1) { switch (c) { case 'v': printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]); exit(EXIT_SUCCESS); case 's': options->show_scores = 1; break; case 'q': options->init_search = optarg; break; case 'e': options->filter = optarg; break; case 'b': if (optarg) { if (sscanf(optarg, "%d", &options->benchmark) != 1) { usage(argv[0]); exit(EXIT_FAILURE); } } else { options->benchmark = 100; } break; case 't': options->tty_filename = optarg; break; case 'p': options->prompt = optarg; break; case 'j': if (sscanf(optarg, "%u", &options->workers) != 1) { usage(argv[0]); exit(EXIT_FAILURE); } break; case 'l': { int l; if (!strcmp(optarg, "max")) { l = INT_MAX; } else if (sscanf(optarg, "%d", &l) != 1 || l < 3) { fprintf(stderr, "Invalid format for --lines: %s\n", optarg); fprintf(stderr, "Must be integer in range 3..\n"); usage(argv[0]); exit(EXIT_FAILURE); } options->num_lines = l; } break; case 'h': default: usage(argv[0]); exit(EXIT_SUCCESS); } } if (optind != argc) { usage(argv[0]); exit(EXIT_FAILURE); } } fzy-1.0/src/options.h000066400000000000000000000006071335200710300146120ustar00rootroot00000000000000#ifndef OPTIONS_H #define OPTIONS_H OPTIONS_H typedef struct { int benchmark; const char *filter; const char *init_search; const char *tty_filename; int show_scores; unsigned int num_lines; unsigned int scrolloff; const char *prompt; unsigned int workers; } options_t; void options_init(options_t *options); void options_parse(options_t *options, int argc, char *argv[]); #endif fzy-1.0/src/tty.c000066400000000000000000000071701335200710300137340ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "tty.h" #include "../config.h" void tty_reset(tty_t *tty) { tcsetattr(tty->fdin, TCSANOW, &tty->original_termios); } void tty_close(tty_t *tty) { tty_reset(tty); fclose(tty->fout); close(tty->fdin); } static void handle_sigwinch(int sig){ (void)sig; } void tty_init(tty_t *tty, const char *tty_filename) { tty->fdin = open(tty_filename, O_RDONLY); if (tty->fdin < 0) { perror("Failed to open tty"); exit(EXIT_FAILURE); } tty->fout = fopen(tty_filename, "w"); if (!tty->fout) { perror("Failed to open tty"); exit(EXIT_FAILURE); } if (setvbuf(tty->fout, NULL, _IOFBF, 4096)) { perror("setvbuf"); exit(EXIT_FAILURE); } if (tcgetattr(tty->fdin, &tty->original_termios)) { perror("tcgetattr"); exit(EXIT_FAILURE); } struct termios new_termios = tty->original_termios; /* * Disable all of * ICANON Canonical input (erase and kill processing). * ECHO Echo. * ISIG Signals from control characters * ICRNL Conversion of CR characters into NL */ new_termios.c_iflag &= ~(ICRNL); new_termios.c_lflag &= ~(ICANON | ECHO | ISIG); if (tcsetattr(tty->fdin, TCSANOW, &new_termios)) perror("tcsetattr"); tty_getwinsz(tty); tty_setnormal(tty); signal(SIGWINCH, handle_sigwinch); } void tty_getwinsz(tty_t *tty) { struct winsize ws; if (ioctl(fileno(tty->fout), TIOCGWINSZ, &ws) == -1) { tty->maxwidth = 80; tty->maxheight = 25; } else { tty->maxwidth = ws.ws_col; tty->maxheight = ws.ws_row; } } char tty_getchar(tty_t *tty) { char ch; int size = read(tty->fdin, &ch, 1); if (size < 0) { perror("error reading from tty"); exit(EXIT_FAILURE); } else if (size == 0) { /* EOF */ exit(EXIT_FAILURE); } else { return ch; } } int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal) { fd_set readfs; FD_ZERO(&readfs); FD_SET(tty->fdin, &readfs); struct timespec ts = {timeout / 1000, (timeout % 1000) * 1000000}; sigset_t mask; sigemptyset(&mask); if (!return_on_signal) sigaddset(&mask, SIGWINCH); int err = pselect( tty->fdin + 1, &readfs, NULL, NULL, timeout < 0 ? NULL : &ts, return_on_signal ? NULL : &mask); if (err < 0) { if (errno == EINTR) { return 0; } else { perror("select"); exit(EXIT_FAILURE); } } else { return FD_ISSET(tty->fdin, &readfs); } } static void tty_sgr(tty_t *tty, int code) { tty_printf(tty, "%c%c%im", 0x1b, '[', code); } void tty_setfg(tty_t *tty, int fg) { if (tty->fgcolor != fg) { tty_sgr(tty, 30 + fg); tty->fgcolor = fg; } } void tty_setinvert(tty_t *tty) { tty_sgr(tty, 7); } void tty_setunderline(tty_t *tty) { tty_sgr(tty, 4); } void tty_setnormal(tty_t *tty) { tty_sgr(tty, 0); tty->fgcolor = 9; } void tty_setnowrap(tty_t *tty) { tty_printf(tty, "%c%c?7l", 0x1b, '['); } void tty_setwrap(tty_t *tty) { tty_printf(tty, "%c%c?7h", 0x1b, '['); } void tty_newline(tty_t *tty) { tty_printf(tty, "%c%cK\n", 0x1b, '['); } void tty_clearline(tty_t *tty) { tty_printf(tty, "%c%cK", 0x1b, '['); } void tty_setcol(tty_t *tty, int col) { tty_printf(tty, "%c%c%iG", 0x1b, '[', col + 1); } void tty_moveup(tty_t *tty, int i) { tty_printf(tty, "%c%c%iA", 0x1b, '[', i); } void tty_printf(tty_t *tty, const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(tty->fout, fmt, args); va_end(args); } void tty_flush(tty_t *tty) { fflush(tty->fout); } size_t tty_getwidth(tty_t *tty) { return tty->maxwidth; } size_t tty_getheight(tty_t *tty) { return tty->maxheight; } fzy-1.0/src/tty.h000066400000000000000000000025271335200710300137420ustar00rootroot00000000000000#ifndef TTY_H #define TTY_H TTY_H #include typedef struct { int fdin; FILE *fout; struct termios original_termios; int fgcolor; size_t maxwidth; size_t maxheight; } tty_t; void tty_reset(tty_t *tty); void tty_close(tty_t *tty); void tty_init(tty_t *tty, const char *tty_filename); void tty_getwinsz(tty_t *tty); char tty_getchar(tty_t *tty); int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal); void tty_setfg(tty_t *tty, int fg); void tty_setinvert(tty_t *tty); void tty_setunderline(tty_t *tty); void tty_setnormal(tty_t *tty); void tty_setnowrap(tty_t *tty); void tty_setwrap(tty_t *tty); #define TTY_COLOR_BLACK 0 #define TTY_COLOR_RED 1 #define TTY_COLOR_GREEN 2 #define TTY_COLOR_YELLOW 3 #define TTY_COLOR_BLUE 4 #define TTY_COLOR_MAGENTA 5 #define TTY_COLOR_CYAN 6 #define TTY_COLOR_WHITE 7 #define TTY_COLOR_NORMAL 9 /* tty_newline * Move cursor to the beginning of the next line, clearing to the end of the * current line */ void tty_newline(tty_t *tty); /* tty_clearline * Clear to the end of the current line without advancing the cursor. */ void tty_clearline(tty_t *tty); void tty_moveup(tty_t *tty, int i); void tty_setcol(tty_t *tty, int col); void tty_printf(tty_t *tty, const char *fmt, ...); void tty_flush(tty_t *tty); size_t tty_getwidth(tty_t *tty); size_t tty_getheight(tty_t *tty); #endif fzy-1.0/src/tty_interface.c000066400000000000000000000243531335200710300157560ustar00rootroot00000000000000#include #include #include #include #include "match.h" #include "tty_interface.h" #include "../config.h" static int isprint_unicode(char c) { return isprint(c) || c & (1 << 7); } static int is_boundary(char c) { return ~c & (1 << 7) || c & (1 << 6); } static void clear(tty_interface_t *state) { tty_t *tty = state->tty; tty_setcol(tty, 0); size_t line = 0; while (line++ < state->options->num_lines) { tty_newline(tty); } tty_clearline(tty); if (state->options->num_lines > 0) { tty_moveup(tty, line - 1); } tty_flush(tty); } static void draw_match(tty_interface_t *state, const char *choice, int selected) { tty_t *tty = state->tty; options_t *options = state->options; char *search = state->last_search; int n = strlen(search); size_t positions[n + 1]; for (int i = 0; i < n + 1; i++) positions[i] = -1; score_t score = match_positions(search, choice, &positions[0]); if (options->show_scores) { if (score == SCORE_MIN) { tty_printf(tty, "( ) "); } else { tty_printf(tty, "(%5.2f) ", score); } } if (selected) #ifdef TTY_SELECTION_UNDERLINE tty_setunderline(tty); #else tty_setinvert(tty); #endif tty_setnowrap(tty); for (size_t i = 0, p = 0; choice[i] != '\0'; i++) { if (positions[p] == i) { tty_setfg(tty, TTY_COLOR_HIGHLIGHT); p++; } else { tty_setfg(tty, TTY_COLOR_NORMAL); } tty_printf(tty, "%c", choice[i]); } tty_setwrap(tty); tty_setnormal(tty); } static void draw(tty_interface_t *state) { tty_t *tty = state->tty; choices_t *choices = state->choices; options_t *options = state->options; unsigned int num_lines = options->num_lines; size_t start = 0; size_t current_selection = choices->selection; if (current_selection + options->scrolloff >= num_lines) { start = current_selection + options->scrolloff - num_lines + 1; size_t available = choices_available(choices); if (start + num_lines >= available && available > 0) { start = available - num_lines; } } tty_setcol(tty, 0); tty_printf(tty, "%s%s", options->prompt, state->search); tty_clearline(tty); for (size_t i = start; i < start + num_lines; i++) { tty_printf(tty, "\n"); tty_clearline(tty); const char *choice = choices_get(choices, i); if (choice) { draw_match(state, choice, i == choices->selection); } } if (num_lines > 0) { tty_moveup(tty, num_lines); } tty_setcol(tty, 0); fputs(options->prompt, tty->fout); for (size_t i = 0; i < state->cursor; i++) fputc(state->search[i], tty->fout); tty_flush(tty); } static void update_search(tty_interface_t *state) { choices_search(state->choices, state->search); strcpy(state->last_search, state->search); } static void update_state(tty_interface_t *state) { if (strcmp(state->last_search, state->search)) { update_search(state); draw(state); } } static void action_emit(tty_interface_t *state) { update_state(state); /* Reset the tty as close as possible to the previous state */ clear(state); /* ttyout should be flushed before outputting on stdout */ tty_close(state->tty); const char *selection = choices_get(state->choices, state->choices->selection); if (selection) { /* output the selected result */ printf("%s\n", selection); } else { /* No match, output the query instead */ printf("%s\n", state->search); } state->exit = EXIT_SUCCESS; } static void action_del_char(tty_interface_t *state) { size_t length = strlen(state->search); if (state->cursor == 0) { return; } size_t original_cursor = state->cursor; do { state->cursor--; } while (!is_boundary(state->search[state->cursor]) && state->cursor); memmove(&state->search[state->cursor], &state->search[original_cursor], length - original_cursor + 1); } static void action_del_word(tty_interface_t *state) { size_t original_cursor = state->cursor; size_t cursor = state->cursor; while (cursor && isspace(state->search[cursor - 1])) cursor--; while (cursor && !isspace(state->search[cursor - 1])) cursor--; memmove(&state->search[cursor], &state->search[original_cursor], strlen(state->search) - original_cursor + 1); state->cursor = cursor; } static void action_del_all(tty_interface_t *state) { memmove(state->search, &state->search[state->cursor], strlen(state->search) - state->cursor + 1); state->cursor = 0; } static void action_prev(tty_interface_t *state) { update_state(state); choices_prev(state->choices); } static void action_ignore(tty_interface_t *state) { (void)state; } static void action_next(tty_interface_t *state) { update_state(state); choices_next(state->choices); } static void action_left(tty_interface_t *state) { if (state->cursor > 0) { state->cursor--; while (!is_boundary(state->search[state->cursor]) && state->cursor) state->cursor--; } } static void action_right(tty_interface_t *state) { if (state->cursor < strlen(state->search)) { state->cursor++; while (!is_boundary(state->search[state->cursor])) state->cursor++; } } static void action_beginning(tty_interface_t *state) { state->cursor = 0; } static void action_end(tty_interface_t *state) { state->cursor = strlen(state->search); } static void action_pageup(tty_interface_t *state) { update_state(state); for (size_t i = 0; i < state->options->num_lines && state->choices->selection > 0; i++) choices_prev(state->choices); } static void action_pagedown(tty_interface_t *state) { update_state(state); for (size_t i = 0; i < state->options->num_lines && state->choices->selection < state->choices->available - 1; i++) choices_next(state->choices); } static void action_autocomplete(tty_interface_t *state) { update_state(state); const char *current_selection = choices_get(state->choices, state->choices->selection); if (current_selection) { strncpy(state->search, choices_get(state->choices, state->choices->selection), SEARCH_SIZE_MAX); state->cursor = strlen(state->search); } } static void action_exit(tty_interface_t *state) { clear(state); tty_close(state->tty); state->exit = EXIT_FAILURE; } static void append_search(tty_interface_t *state, char ch) { char *search = state->search; size_t search_size = strlen(search); if (search_size < SEARCH_SIZE_MAX) { memmove(&search[state->cursor+1], &search[state->cursor], search_size - state->cursor + 1); search[state->cursor] = ch; state->cursor++; } } void tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options) { state->tty = tty; state->choices = choices; state->options = options; state->ambiguous_key_pending = 0; strcpy(state->input, ""); strcpy(state->search, ""); strcpy(state->last_search, ""); state->exit = -1; if (options->init_search) strncpy(state->search, options->init_search, SEARCH_SIZE_MAX); state->cursor = strlen(state->search); update_search(state); } typedef struct { const char *key; void (*action)(tty_interface_t *); } keybinding_t; #define KEY_CTRL(key) ((const char[]){((key) - ('@')), '\0'}) static const keybinding_t keybindings[] = {{"\x1b", action_exit}, /* ESC */ {"\x7f", action_del_char}, /* DEL */ {KEY_CTRL('H'), action_del_char}, /* Backspace (C-H) */ {KEY_CTRL('W'), action_del_word}, /* C-W */ {KEY_CTRL('U'), action_del_all}, /* C-U */ {KEY_CTRL('I'), action_autocomplete}, /* TAB (C-I ) */ {KEY_CTRL('C'), action_exit}, /* C-C */ {KEY_CTRL('D'), action_exit}, /* C-D */ {KEY_CTRL('M'), action_emit}, /* CR */ {KEY_CTRL('P'), action_prev}, /* C-P */ {KEY_CTRL('N'), action_next}, /* C-N */ {KEY_CTRL('K'), action_prev}, /* C-K */ {KEY_CTRL('J'), action_next}, /* C-J */ {KEY_CTRL('A'), action_beginning}, /* C-A */ {KEY_CTRL('E'), action_end}, /* C-E */ {"\x1bOD", action_left}, /* LEFT */ {"\x1b[D", action_left}, /* LEFT */ {"\x1bOC", action_right}, /* RIGHT */ {"\x1b[C", action_right}, /* RIGHT */ {"\x1b[1~", action_beginning}, /* HOME */ {"\x1b[H", action_beginning}, /* HOME */ {"\x1b[4~", action_end}, /* END */ {"\x1b[F", action_end}, /* END */ {"\x1b[A", action_prev}, /* UP */ {"\x1bOA", action_prev}, /* UP */ {"\x1b[B", action_next}, /* DOWN */ {"\x1bOB", action_next}, /* DOWN */ {"\x1b[5~", action_pageup}, {"\x1b[6~", action_pagedown}, {"\x1b[200~", action_ignore}, {"\x1b[201~", action_ignore}, {NULL, NULL}}; #undef KEY_CTRL static void handle_input(tty_interface_t *state, const char *s, int handle_ambiguous_key) { state->ambiguous_key_pending = 0; char *input = state->input; strcat(state->input, s); /* Figure out if we have completed a keybinding and whether we're in the * middle of one (both can happen, because of Esc). */ int found_keybinding = -1; int in_middle = 0; for (int i = 0; keybindings[i].key; i++) { if (!strcmp(input, keybindings[i].key)) found_keybinding = i; else if (!strncmp(input, keybindings[i].key, strlen(state->input))) in_middle = 1; } /* If we have an unambiguous keybinding, run it. */ if (found_keybinding != -1 && (!in_middle || handle_ambiguous_key)) { keybindings[found_keybinding].action(state); strcpy(input, ""); return; } /* We could have a complete keybinding, or could be in the middle of one. * We'll need to wait a few milliseconds to find out. */ if (found_keybinding != -1 && in_middle) { state->ambiguous_key_pending = 1; return; } /* Wait for more if we are in the middle of a keybinding */ if (in_middle) return; /* No matching keybinding, add to search */ for (int i = 0; input[i]; i++) if (isprint_unicode(input[i])) append_search(state, input[i]); /* We have processed the input, so clear it */ strcpy(input, ""); } int tty_interface_run(tty_interface_t *state) { draw(state); for (;;) { do { while(!tty_input_ready(state->tty, -1, 1)) { /* We received a signal (probably WINCH) */ draw(state); } char s[2] = {tty_getchar(state->tty), '\0'}; handle_input(state, s, 0); if (state->exit >= 0) return state->exit; draw(state); } while (tty_input_ready(state->tty, state->ambiguous_key_pending ? KEYTIMEOUT : 0, 0)); if (state->ambiguous_key_pending) { char s[1] = ""; handle_input(state, s, 1); if (state->exit >= 0) return state->exit; } update_state(state); } return state->exit; } fzy-1.0/src/tty_interface.h000066400000000000000000000011041335200710300157500ustar00rootroot00000000000000#ifndef TTY_INTERFACE_H #define TTY_INTERFACE_H TTY_INTERFACE_H #include "choices.h" #include "options.h" #include "tty.h" #define SEARCH_SIZE_MAX 4096 typedef struct { tty_t *tty; choices_t *choices; options_t *options; char search[SEARCH_SIZE_MAX + 1]; char last_search[SEARCH_SIZE_MAX + 1]; size_t cursor; int ambiguous_key_pending; char input[32]; /* Pending input buffer */ int exit; } tty_interface_t; void tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options); int tty_interface_run(tty_interface_t *state); #endif fzy-1.0/test/000077500000000000000000000000001335200710300131335ustar00rootroot00000000000000fzy-1.0/test/acceptance/000077500000000000000000000000001335200710300152215ustar00rootroot00000000000000fzy-1.0/test/acceptance/Gemfile000066400000000000000000000000731335200710300165140ustar00rootroot00000000000000source 'https://rubygems.org' gem 'ttytest' gem 'minitest' fzy-1.0/test/acceptance/Gemfile.lock000066400000000000000000000002441335200710300174430ustar00rootroot00000000000000GEM remote: https://rubygems.org/ specs: minitest (5.10.1) ttytest (0.4.0) PLATFORMS ruby DEPENDENCIES minitest ttytest BUNDLED WITH 1.13.7 fzy-1.0/test/acceptance/acceptance_test.rb000066400000000000000000000302221335200710300206720ustar00rootroot00000000000000require 'minitest' require 'minitest/autorun' require 'ttytest' class FzyTest < Minitest::Test FZY_PATH = File.expand_path('../../../fzy', __FILE__) LEFT = "\e[D" RIGHT = "\e[C" def test_empty_list @tty = interactive_fzy(input: %w[], before: "placeholder") @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder > TTY @tty.send_keys('t') @tty.assert_cursor_position(y: 1, x: 3) @tty.assert_matches <<~TTY placeholder > t TTY @tty.send_keys('z') @tty.assert_cursor_position(y: 1, x: 4) @tty.assert_matches <<~TTY placeholder > tz TTY @tty.send_keys("\r") @tty.assert_cursor_position(y: 2, x: 0) @tty.assert_matches <<~TTY placeholder tz TTY end def test_one_item @tty = interactive_fzy(input: %w[test], before: "placeholder") @tty.assert_matches <<~TTY placeholder > test TTY @tty.assert_cursor_position(y: 1, x: 2) @tty.send_keys('t') @tty.assert_cursor_position(y: 1, x: 3) @tty.assert_matches <<~TTY placeholder > t test TTY @tty.send_keys('z') @tty.assert_cursor_position(y: 1, x: 4) @tty.assert_matches <<~TTY placeholder > tz TTY @tty.send_keys("\r") @tty.assert_cursor_position(y: 2, x: 0) @tty.assert_matches <<~TTY placeholder tz TTY end def test_two_items @tty = interactive_fzy(input: %w[test foo], before: "placeholder") @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder > test foo TTY @tty.send_keys('t') @tty.assert_cursor_position(y: 1, x: 3) @tty.assert_matches <<~TTY placeholder > t test TTY @tty.send_keys('z') @tty.assert_cursor_position(y: 1, x: 4) @tty.assert_matches <<~TTY placeholder > tz TTY @tty.send_keys("\r") @tty.assert_matches <<~TTY placeholder tz TTY @tty.assert_cursor_position(y: 2, x: 0) end def ctrl(key) ((key.upcase.ord) - ('A'.ord) + 1).chr end def test_editing @tty = interactive_fzy(input: %w[test foo], before: "placeholder") @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder > test foo TTY @tty.send_keys("foo bar baz") @tty.assert_cursor_position(y: 1, x: 13) @tty.assert_matches <<~TTY placeholder > foo bar baz TTY @tty.send_keys(ctrl('H')) @tty.assert_cursor_position(y: 1, x: 12) @tty.assert_matches <<~TTY placeholder > foo bar ba TTY @tty.send_keys(ctrl('W')) @tty.assert_cursor_position(y: 1, x: 10) @tty.assert_matches <<~TTY placeholder > foo bar TTY @tty.send_keys(ctrl('U')) @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder > test foo TTY end def test_ctrl_d @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys('foo') @tty.assert_matches "> foo\nfoo" @tty.send_keys(ctrl('D')) @tty.assert_matches '' @tty.assert_cursor_position(y: 0, x: 0) end def test_ctrl_c @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys('foo') @tty.assert_matches "> foo\nfoo" @tty.send_keys(ctrl('C')) @tty.assert_matches '' @tty.assert_cursor_position(y: 0, x: 0) end def test_down_arrow @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\e[A\r") @tty.assert_matches "bar" @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\eOA\r") @tty.assert_matches "bar" end def test_up_arrow @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\e[A") # first down @tty.send_keys("\e[B\r") # and back up @tty.assert_matches "foo" @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\eOA") # first down @tty.send_keys("\e[B\r") # and back up @tty.assert_matches "foo" end def test_lines input10 = (1..10).map(&:to_s) input20 = (1..20).map(&:to_s) @tty = interactive_fzy(input: input10) @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10" @tty = interactive_fzy(input: input20) @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10" @tty = interactive_fzy(input: input10, args: "-l 5") @tty.assert_matches ">\n1\n2\n3\n4\n5" @tty = interactive_fzy(input: input10, args: "--lines=5") @tty.assert_matches ">\n1\n2\n3\n4\n5" end def test_prompt @tty = interactive_fzy @tty.send_keys("foo") @tty.assert_matches '> foo' @tty = interactive_fzy(args: "-p 'C:\\'") @tty.send_keys("foo") @tty.assert_matches 'C:\foo' @tty = interactive_fzy(args: "--prompt=\"foo bar \"") @tty.send_keys("baz") @tty.assert_matches "foo bar baz" end def test_show_scores expected_score = '( inf)' @tty = interactive_fzy(input: %w[foo bar], args: "-s") @tty.send_keys('foo') @tty.assert_matches "> foo\n#{expected_score} foo" @tty = interactive_fzy(input: %w[foo bar], args: "--show-scores") @tty.send_keys('foo') @tty.assert_matches "> foo\n#{expected_score} foo" expected_score = '( 0.89)' @tty = interactive_fzy(input: %w[foo bar], args: "-s") @tty.send_keys('f') @tty.assert_matches "> f\n#{expected_score} foo" end def test_large_input @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -l 3}) @tty.send_keys('34') @tty.assert_matches "> 34\n34\n340\n341" @tty.send_keys('5') @tty.assert_matches "> 345\n345\n3450\n3451" @tty.send_keys('z') @tty.assert_matches "> 345z" end def test_worker_count @tty = interactive_fzy(input: %w[foo bar], args: "-j1") @tty.send_keys('foo') @tty.assert_matches "> foo\nfoo" @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -j1 -l3}) @tty.send_keys('34') @tty.assert_matches "> 34\n34\n340\n341" @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -j200 -l3}) @tty.send_keys('34') @tty.assert_matches "> 34\n34\n340\n341" end def test_initial_query @tty = interactive_fzy(input: %w[foo bar], args: "-q fo") @tty.assert_matches "> fo\nfoo" @tty.send_keys("o") @tty.assert_matches "> foo\nfoo" @tty.send_keys("o") @tty.assert_matches "> fooo" @tty = interactive_fzy(input: %w[foo bar], args: "-q asdf") @tty.assert_matches "> asdf" end def test_non_interactive @tty = interactive_fzy(input: %w[foo bar], args: "-e foo", before: "before", after: "after") @tty.assert_matches "before\nfoo\nafter" end def test_moving_text_cursor @tty = interactive_fzy(input: %w[foo bar]) @tty.send_keys("br") @tty.assert_matches "> br\nbar" @tty.assert_cursor_position(y: 0, x: 4) @tty.send_keys(LEFT) @tty.assert_cursor_position(y: 0, x: 3) @tty.assert_matches "> br\nbar" @tty.send_keys("a") @tty.assert_cursor_position(y: 0, x: 4) @tty.assert_matches "> bar\nbar" @tty.send_keys(ctrl("A")) # Ctrl-A @tty.assert_cursor_position(y: 0, x: 2) @tty.assert_matches "> bar\nbar" @tty.send_keys("foo") @tty.assert_cursor_position(y: 0, x: 5) @tty.assert_matches "> foobar" @tty.send_keys(ctrl("E")) # Ctrl-E @tty.assert_cursor_position(y: 0, x: 8) @tty.assert_matches "> foobar" @tty.send_keys("baz") # Ctrl-E @tty.assert_cursor_position(y: 0, x: 11) @tty.assert_matches "> foobarbaz" end # More info; # https://github.com/jhawthorn/fzy/issues/42 # https://cirw.in/blog/bracketed-paste def test_bracketed_paste_characters @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\e[200~foo\e[201~") @tty.assert_matches "> foo\nfoo" end # https://github.com/jhawthorn/fzy/issues/81 def test_slow_stdin_fast_user @tty = TTYtest.new_terminal(%{(sleep 0.5; echo aa; echo bc; echo bd) | #{FZY_PATH}}) # Before input has all come in, but wait for fzy to at least start sleep 0.1 @tty.send_keys("b\r") @tty.assert_matches "bc" end def test_unicode @tty = interactive_fzy(input: %w[English Français 日本語]) @tty.assert_matches <<~TTY > English Français 日本語 TTY @tty.assert_cursor_position(y: 0, x: 2) @tty.send_keys("ç") @tty.assert_matches <<~TTY > ç Français TTY @tty.assert_cursor_position(y: 0, x: 3) @tty.send_keys("\r") @tty.assert_matches "Français" end def test_unicode_backspace @tty = interactive_fzy @tty.send_keys "Français" @tty.assert_matches "> Français" @tty.assert_cursor_position(y: 0, x: 10) @tty.send_keys(ctrl('H') * 3) @tty.assert_matches "> Franç" @tty.assert_cursor_position(y: 0, x: 7) @tty.send_keys(ctrl('H')) @tty.assert_matches "> Fran" @tty.assert_cursor_position(y: 0, x: 6) @tty.send_keys('ce') @tty.assert_matches "> France" @tty = interactive_fzy @tty.send_keys "日本語" @tty.assert_matches "> 日本語" @tty.send_keys(ctrl('H')) @tty.assert_matches "> 日本" @tty.send_keys(ctrl('H')) @tty.assert_matches "> 日" @tty.send_keys(ctrl('H')) @tty.assert_matches "> " @tty.assert_cursor_position(y: 0, x: 2) end def test_unicode_delete_word @tty = interactive_fzy @tty.send_keys "Je parle Français" @tty.assert_matches "> Je parle Français" @tty.assert_cursor_position(y: 0, x: 19) @tty.send_keys(ctrl('W')) @tty.assert_matches "> Je parle" @tty.assert_cursor_position(y: 0, x: 11) @tty = interactive_fzy @tty.send_keys "日本語" @tty.assert_matches "> 日本語" @tty.send_keys(ctrl('W')) @tty.assert_matches "> " @tty.assert_cursor_position(y: 0, x: 2) end def test_unicode_cursor_movement @tty = interactive_fzy @tty.send_keys "Français" @tty.assert_cursor_position(y: 0, x: 10) @tty.send_keys(LEFT*5) @tty.assert_cursor_position(y: 0, x: 5) @tty.send_keys(RIGHT*3) @tty.assert_cursor_position(y: 0, x: 8) @tty = interactive_fzy @tty.send_keys "日本語" @tty.assert_matches "> 日本語" @tty.assert_cursor_position(y: 0, x: 8) @tty.send_keys(LEFT) @tty.assert_cursor_position(y: 0, x: 6) @tty.send_keys(LEFT) @tty.assert_cursor_position(y: 0, x: 4) @tty.send_keys(LEFT) @tty.assert_cursor_position(y: 0, x: 2) @tty.send_keys(LEFT) @tty.assert_cursor_position(y: 0, x: 2) @tty.send_keys(RIGHT*3) @tty.assert_cursor_position(y: 0, x: 8) @tty.send_keys(RIGHT) @tty.assert_cursor_position(y: 0, x: 8) end def test_long_strings ascii = "LongStringOfText" * 6 unicode = "LongStringOfText" * 3 @tty = interactive_fzy(input: [ascii, unicode]) @tty.assert_matches <<~TTY > LongStringOfTextLongStringOfTextLongStringOfTextLongStringOfTextLongStringOfText LongStringOfTextLongStringOfTextLongStri TTY end def test_help @tty = TTYtest.new_terminal(%{#{FZY_PATH} --help}) @tty.assert_matches < ') -q, --query=QUERY Use QUERY as the initial search string -e, --show-matches=QUERY Output the sorted matches of QUERY -t, --tty=TTY Specify file to use as TTY device (default /dev/tty) -s, --show-scores Show the scores of each match -j, --workers NUM Use NUM workers for searching. (default is # of CPUs) -h, --help Display this help and exit -v, --version Output version information and exit TTY end private def interactive_fzy(input: [], before: nil, after: nil, args: "") cmd = [] cmd << %{echo "#{before}"} if before cmd << %{printf "#{input.join("\\n")}" | #{FZY_PATH} #{args}} cmd << %{echo "#{after}"} if after cmd = cmd.join("; ") TTYtest.new_terminal(cmd) end end fzy-1.0/test/fzytest.c000066400000000000000000000004421335200710300150070ustar00rootroot00000000000000#include "greatest/greatest.h" SUITE(match_suite); SUITE(choices_suite); SUITE(properties_suite); GREATEST_MAIN_DEFS(); int main(int argc, char *argv[]) { GREATEST_MAIN_BEGIN(); RUN_SUITE(match_suite); RUN_SUITE(choices_suite); RUN_SUITE(properties_suite); GREATEST_MAIN_END(); } fzy-1.0/test/test_choices.c000066400000000000000000000075521335200710300157640ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include "../config.h" #include "options.h" #include "choices.h" #include "greatest/greatest.h" #define ASSERT_SIZE_T_EQ(a,b) ASSERT_EQ_FMT((size_t)(a), (b), "%zu") static options_t default_options; static choices_t choices; static void setup(void *udata) { (void)udata; options_init(&default_options); choices_init(&choices, &default_options); } static void teardown(void *udata) { (void)udata; choices_destroy(&choices); } TEST test_choices_empty() { ASSERT_SIZE_T_EQ(0, choices.size); ASSERT_SIZE_T_EQ(0, choices.available); ASSERT_SIZE_T_EQ(0, choices.selection); choices_prev(&choices); ASSERT_SIZE_T_EQ(0, choices.selection); choices_next(&choices); ASSERT_SIZE_T_EQ(0, choices.selection); PASS(); } TEST test_choices_1() { choices_add(&choices, "tags"); choices_search(&choices, ""); ASSERT_SIZE_T_EQ(1, choices.available); ASSERT_SIZE_T_EQ(0, choices.selection); choices_search(&choices, "t"); ASSERT_SIZE_T_EQ(1, choices.available); ASSERT_SIZE_T_EQ(0, choices.selection); choices_prev(&choices); ASSERT_SIZE_T_EQ(0, choices.selection); choices_next(&choices); ASSERT_SIZE_T_EQ(0, choices.selection); ASSERT(!strcmp(choices_get(&choices, 0), "tags")); ASSERT_EQ(NULL, choices_get(&choices, 1)); PASS(); } TEST test_choices_2() { choices_add(&choices, "tags"); choices_add(&choices, "test"); /* Empty search */ choices_search(&choices, ""); ASSERT_SIZE_T_EQ(0, choices.selection); ASSERT_SIZE_T_EQ(2, choices.available); choices_next(&choices); ASSERT_SIZE_T_EQ(1, choices.selection); choices_next(&choices); ASSERT_SIZE_T_EQ(0, choices.selection); choices_prev(&choices); ASSERT_SIZE_T_EQ(1, choices.selection); choices_prev(&choices); ASSERT_SIZE_T_EQ(0, choices.selection); /* Filtered search */ choices_search(&choices, "te"); ASSERT_SIZE_T_EQ(1, choices.available); ASSERT_SIZE_T_EQ(0, choices.selection); ASSERT_STR_EQ("test", choices_get(&choices, 0)); choices_next(&choices); ASSERT_SIZE_T_EQ(0, choices.selection); choices_prev(&choices); ASSERT_SIZE_T_EQ(0, choices.selection); /* No results */ choices_search(&choices, "foobar"); ASSERT_SIZE_T_EQ(0, choices.available); ASSERT_SIZE_T_EQ(0, choices.selection); /* Different order due to scoring */ choices_search(&choices, "ts"); ASSERT_SIZE_T_EQ(2, choices.available); ASSERT_SIZE_T_EQ(0, choices.selection); ASSERT_STR_EQ("test", choices_get(&choices, 0)); ASSERT_STR_EQ("tags", choices_get(&choices, 1)); PASS(); } TEST test_choices_without_search() { /* Before a search is run, it should return no results */ ASSERT_SIZE_T_EQ(0, choices.available); ASSERT_SIZE_T_EQ(0, choices.selection); ASSERT_SIZE_T_EQ(0, choices.size); ASSERT_EQ(NULL, choices_get(&choices, 0)); choices_add(&choices, "test"); ASSERT_SIZE_T_EQ(0, choices.available); ASSERT_SIZE_T_EQ(0, choices.selection); ASSERT_SIZE_T_EQ(1, choices.size); ASSERT_EQ(NULL, choices_get(&choices, 0)); PASS(); } /* Regression test for segfault */ TEST test_choices_unicode() { choices_add(&choices, "Edmund Husserl - Méditations cartésiennes - Introduction a la phénoménologie.pdf"); choices_search(&choices, "e"); PASS(); } TEST test_choices_large_input() { int N = 100000; char *strings[N]; for(int i = 0; i < N; i++) { asprintf(&strings[i], "%i", i); choices_add(&choices, strings[i]); } choices_search(&choices, "12"); /* Must match `seq 0 99999 | grep '.*1.*2.*' | wc -l` */ ASSERT_SIZE_T_EQ(8146, choices.available); ASSERT_STR_EQ("12", choices_get(&choices, 0)); for(int i = 0; i < N; i++) { free(strings[i]); } PASS(); } SUITE(choices_suite) { SET_SETUP(setup, NULL); SET_TEARDOWN(teardown, NULL); RUN_TEST(test_choices_empty); RUN_TEST(test_choices_1); RUN_TEST(test_choices_2); RUN_TEST(test_choices_without_search); RUN_TEST(test_choices_unicode); RUN_TEST(test_choices_large_input); } fzy-1.0/test/test_match.c000066400000000000000000000143621335200710300154400ustar00rootroot00000000000000#include #include "../config.h" #include "match.h" #include "greatest/greatest.h" #define SCORE_TOLERANCE 0.000001 #define ASSERT_SCORE_EQ(a,b) ASSERT_IN_RANGE((a), (b), SCORE_TOLERANCE) #define ASSERT_SIZE_T_EQ(a,b) ASSERT_EQ_FMT((size_t)(a), (b), "%zu") /* has_match(char *needle, char *haystack) */ TEST exact_match_should_return_true() { ASSERT(has_match("a", "a")); PASS(); } TEST partial_match_should_return_true() { ASSERT(has_match("a", "ab")); ASSERT(has_match("a", "ba")); PASS(); } TEST match_with_delimiters_in_between() { ASSERT(has_match("abc", "a|b|c")); PASS(); } TEST non_match_should_return_false() { ASSERT(!has_match("a", "")); ASSERT(!has_match("a", "b")); ASSERT(!has_match("ass", "tags")); PASS(); } TEST empty_query_should_always_match() { /* match when query is empty */ ASSERT(has_match("", "")); ASSERT(has_match("", "a")); PASS(); } /* match(char *needle, char *haystack) */ TEST should_prefer_starts_of_words() { /* App/Models/Order is better than App/MOdels/zRder */ ASSERT(match("amor", "app/models/order") > match("amor", "app/models/zrder")); PASS(); } TEST should_prefer_consecutive_letters() { /* App/MOdels/foo is better than App/M/fOo */ ASSERT(match("amo", "app/m/foo") < match("amo", "app/models/foo")); PASS(); } TEST should_prefer_contiguous_over_letter_following_period() { /* GEMFIle.Lock < GEMFILe */ ASSERT(match("gemfil", "Gemfile.lock") < match("gemfil", "Gemfile")); PASS(); } TEST should_prefer_shorter_matches() { ASSERT(match("abce", "abcdef") > match("abce", "abc de")); ASSERT(match("abc", " a b c ") > match("abc", " a b c ")); ASSERT(match("abc", " a b c ") > match("abc", " a b c ")); PASS(); } TEST should_prefer_shorter_candidates() { ASSERT(match("test", "tests") > match("test", "testing")); PASS(); } TEST should_prefer_start_of_candidate() { /* Scores first letter highly */ ASSERT(match("test", "testing") > match("test", "/testing")); PASS(); } TEST score_exact_match() { /* Exact match is SCORE_MAX */ ASSERT_SCORE_EQ(SCORE_MAX, match("abc", "abc")); ASSERT_SCORE_EQ(SCORE_MAX, match("aBc", "abC")); PASS(); } TEST score_empty_query() { /* Empty query always results in SCORE_MIN */ ASSERT_SCORE_EQ(SCORE_MIN, match("", "")); ASSERT_SCORE_EQ(SCORE_MIN, match("", "a")); ASSERT_SCORE_EQ(SCORE_MIN, match("", "bb")); PASS(); } TEST score_gaps() { ASSERT_SCORE_EQ(SCORE_GAP_LEADING, match("a", "*a")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2, match("a", "*ba")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_GAP_TRAILING, match("a", "**a*")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_GAP_TRAILING*2, match("a", "**a**")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CONSECUTIVE + SCORE_GAP_TRAILING*2, match("aa", "**aa**")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_GAP_TRAILING + SCORE_GAP_TRAILING, match("aa", "**a*a**")); PASS(); } TEST score_consecutive() { ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CONSECUTIVE, match("aa", "*aa")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CONSECUTIVE*2, match("aaa", "*aaa")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_MATCH_CONSECUTIVE, match("aaa", "*a*aa")); PASS(); } TEST score_slash() { ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_SLASH, match("a", "/a")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_SLASH, match("a", "*/a")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_SLASH + SCORE_MATCH_CONSECUTIVE, match("aa", "a/aa")); PASS(); } TEST score_capital() { ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CAPITAL, match("a", "bA")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CAPITAL, match("a", "baA")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CAPITAL + SCORE_MATCH_CONSECUTIVE, match("aa", "baAa")); PASS(); } TEST score_dot() { ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_DOT, match("a", ".a")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING*3 + SCORE_MATCH_DOT, match("a", "*a.a")); ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_MATCH_DOT, match("a", "*a.a")); PASS(); } TEST positions_consecutive() { size_t positions[3]; match_positions("amo", "app/models/foo", positions); ASSERT_SIZE_T_EQ(0, positions[0]); ASSERT_SIZE_T_EQ(4, positions[1]); ASSERT_SIZE_T_EQ(5, positions[2]); PASS(); } TEST positions_start_of_word() { /* * We should prefer matching the 'o' in order, since it's the beginning * of a word. */ size_t positions[4]; match_positions("amor", "app/models/order", positions); ASSERT_SIZE_T_EQ(0, positions[0]); ASSERT_SIZE_T_EQ(4, positions[1]); ASSERT_SIZE_T_EQ(11, positions[2]); ASSERT_SIZE_T_EQ(12, positions[3]); PASS(); } TEST positions_no_bonuses() { size_t positions[2]; match_positions("as", "tags", positions); ASSERT_SIZE_T_EQ(1, positions[0]); ASSERT_SIZE_T_EQ(3, positions[1]); match_positions("as", "examples.txt", positions); ASSERT_SIZE_T_EQ(2, positions[0]); ASSERT_SIZE_T_EQ(7, positions[1]); PASS(); } TEST positions_multiple_candidates_start_of_words() { size_t positions[3]; match_positions("abc", "a/a/b/c/c", positions); ASSERT_SIZE_T_EQ(2, positions[0]); ASSERT_SIZE_T_EQ(4, positions[1]); ASSERT_SIZE_T_EQ(6, positions[2]); PASS(); } TEST positions_exact_match() { size_t positions[3]; match_positions("foo", "foo", positions); ASSERT_SIZE_T_EQ(0, positions[0]); ASSERT_SIZE_T_EQ(1, positions[1]); ASSERT_SIZE_T_EQ(2, positions[2]); PASS(); } SUITE(match_suite) { RUN_TEST(exact_match_should_return_true); RUN_TEST(partial_match_should_return_true); RUN_TEST(empty_query_should_always_match); RUN_TEST(non_match_should_return_false); RUN_TEST(match_with_delimiters_in_between); RUN_TEST(should_prefer_starts_of_words); RUN_TEST(should_prefer_consecutive_letters); RUN_TEST(should_prefer_contiguous_over_letter_following_period); RUN_TEST(should_prefer_shorter_matches); RUN_TEST(should_prefer_shorter_candidates); RUN_TEST(should_prefer_start_of_candidate); RUN_TEST(score_exact_match); RUN_TEST(score_empty_query); RUN_TEST(score_gaps); RUN_TEST(score_consecutive); RUN_TEST(score_slash); RUN_TEST(score_capital); RUN_TEST(score_dot); RUN_TEST(positions_consecutive); RUN_TEST(positions_start_of_word); RUN_TEST(positions_no_bonuses); RUN_TEST(positions_multiple_candidates_start_of_words); RUN_TEST(positions_exact_match); } fzy-1.0/test/test_properties.c000066400000000000000000000075121335200710300165370ustar00rootroot00000000000000#define _DEFAULT_SOURCE #include #include "greatest/greatest.h" #include "theft/theft.h" #include "match.h" static void *string_alloc_cb(struct theft *t, theft_hash seed, void *env) { (void)env; int limit = 128; size_t sz = (size_t)(seed % limit) + 1; char *str = malloc(sz + 1); if (str == NULL) { return THEFT_ERROR; } for (size_t i = 0; i < sz; i += sizeof(theft_hash)) { theft_hash s = theft_random(t); for (uint8_t b = 0; b < sizeof(theft_hash); b++) { if (i + b >= sz) { break; } str[i + b] = (uint8_t)(s >> (8 * b)) & 0xff; } } str[sz] = 0; return str; } static void string_free_cb(void *instance, void *env) { free(instance); (void)env; } static void string_print_cb(FILE *f, void *instance, void *env) { char *str = (char *)instance; (void)env; size_t size = strlen(str); fprintf(f, "str[%zd]:\n ", size); uint8_t bytes = 0; for (size_t i = 0; i < size; i++) { fprintf(f, "%02x", str[i]); bytes++; if (bytes == 16) { fprintf(f, "\n "); bytes = 0; } } fprintf(f, "\n"); } static uint64_t string_hash_cb(void *instance, void *env) { (void)env; char *str = (char *)instance; int size = strlen(str); return theft_hash_onepass((uint8_t *)str, size); } static void *string_shrink_cb(void *instance, uint32_t tactic, void *env) { (void)env; char *str = (char *)instance; int n = strlen(str); if (tactic == 0) { /* first half */ return strndup(str, n / 2); } else if (tactic == 1) { /* second half */ return strndup(str + (n / 2), n / 2); } else { return THEFT_NO_MORE_TACTICS; } } static struct theft_type_info string_info = { .alloc = string_alloc_cb, .free = string_free_cb, .print = string_print_cb, .hash = string_hash_cb, .shrink = string_shrink_cb, }; static theft_trial_res prop_should_return_results_if_there_is_a_match(char *needle, char *haystack) { int match_exists = has_match(needle, haystack); if (!match_exists) return THEFT_TRIAL_SKIP; score_t score = match(needle, haystack); if (needle[0] == '\0') return THEFT_TRIAL_SKIP; if (score == SCORE_MIN) return THEFT_TRIAL_FAIL; return THEFT_TRIAL_PASS; } TEST should_return_results_if_there_is_a_match() { struct theft *t = theft_init(0); struct theft_cfg cfg = { .name = __func__, .fun = prop_should_return_results_if_there_is_a_match, .type_info = {&string_info, &string_info}, .trials = 100000, }; theft_run_res res = theft_run(t, &cfg); theft_free(t); GREATEST_ASSERT_EQm("should_return_results_if_there_is_a_match", THEFT_RUN_PASS, res); PASS(); } static theft_trial_res prop_positions_should_match_characters_in_string(char *needle, char *haystack) { int match_exists = has_match(needle, haystack); if (!match_exists) return THEFT_TRIAL_SKIP; int n = strlen(needle); size_t *positions = calloc(n, sizeof(size_t)); if (!positions) return THEFT_TRIAL_ERROR; match_positions(needle, haystack, positions); /* Must be increasing */ for (int i = 1; i < n; i++) { if (positions[i] <= positions[i - 1]) { return THEFT_TRIAL_FAIL; } } /* Matching characters must be in returned positions */ for (int i = 0; i < n; i++) { if (toupper(needle[i]) != toupper(haystack[positions[i]])) { return THEFT_TRIAL_FAIL; } } free(positions); return THEFT_TRIAL_PASS; } TEST positions_should_match_characters_in_string() { struct theft *t = theft_init(0); struct theft_cfg cfg = { .name = __func__, .fun = prop_positions_should_match_characters_in_string, .type_info = {&string_info, &string_info}, .trials = 100000, }; theft_run_res res = theft_run(t, &cfg); theft_free(t); GREATEST_ASSERT_EQm("should_return_results_if_there_is_a_match", THEFT_RUN_PASS, res); PASS(); } SUITE(properties_suite) { RUN_TEST(should_return_results_if_there_is_a_match); RUN_TEST(positions_should_match_characters_in_string); }