summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilliam Langford <wlangford@digitalocean.com>2019-10-22 12:37:19 -0400
committerWilliam Langford <wlangfor@gmail.com>2020-03-02 11:05:43 -0500
commit50a7022ea68fb37faefdcd7a35661df72fbfd655 (patch)
treeaf2268e7b3fb8c04bcc7ccbf705b3dfab4bbe5e6
parent8c61ebad0aaee3f5293c5e5077c0aeb0e8c232ec (diff)
Rework pipenv requirement to be more relaxed
Keep a cached copy of the man tests that we can use when no manpage changes are made. This allows automated systems that might not have easy access to a pipenv to build and run tests.
-rw-r--r--Makefile.am39
-rw-r--r--configure.ac37
-rw-r--r--tests/man.test843
-rwxr-xr-xtests/mantest3
4 files changed, 892 insertions, 30 deletions
diff --git a/Makefile.am b/Makefile.am
index 9ff75274..8bbabb12 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -130,27 +130,46 @@ endif
### Tests (make check)
-if ENABLE_DOCS
TESTS = tests/optionaltest tests/mantest tests/jqtest tests/onigtest tests/shtest tests/utf8test tests/base64test
-else
-TESTS = tests/optionaltest tests/jqtest tests/onigtest tests/shtest tests/utf8test tests/base64test
-endif
TESTS_ENVIRONMENT = NO_VALGRIND=$(NO_VALGRIND)
+# This is a magic make variable that causes it to treat tests/man.test as a
+# DATA-type dependency for the check target. As a result, it will attempt to
+# run any defined targets for tests/man.test as a dependency for check. This
+# allows us to ensure that the tests are up-to-date if the manual has been updated
+check_DATA = tests/man.test
+
+### Building the man tests
+
+# We use the examples in the manual as additional tests, to ensure they always work.
+# As a result, we need to rebuild the tests if the manual has been updated.
+# Making changes to the manpage without having the python deps means your
+# tests won't run. If you aren't making changes to the examples, you probably
+# don't care. But if you are, then you need to run the tests anyway.
+tests/man.test: $(srcdir)/docs/content/manual/manual.yml
+if ENABLE_DOCS
+ $(AM_V_GEN) ( cd ${abs_srcdir}/docs; $(PIPENV) run python build_mantests.py ) > $@
+else
+ @echo Changes to the manual.yml require docs to be enabled to run the tests
+ @false
+endif
### Building the manpage
+# We build the docs from the manpage yml. If no changes have been made to the
+# manpage, then we'll end up using the cached version. Otherwise, we need to
+# rebuild it.
man_MANS = jq.1
+jq.1.prebuilt: $(srcdir)/docs/content/manual/manual.yml
if ENABLE_DOCS
-jq.1: $(srcdir)/docs/content/manual/manual.yml
- $(AM_V_GEN) ( cd ${abs_srcdir}/docs; pipenv run python build_manpage.py ) > $@ || { rm -f $@; false; }
-jq.1.prebuilt: jq.1
- $(AM_V_GEN) cp jq.1 $@ || { rm -f $@; false; }
+ $(AM_V_GEN) ( cd ${abs_srcdir}/docs; $(PIPENV) run python build_manpage.py ) > $@ || { rm -f $@; false; }
else
-jq.1: $(srcdir)/jq.1.prebuilt
- $(AM_V_GEN) cp $(srcdir)/jq.1.prebuilt $@
+ @echo Changes to the manual.yml require docs to be enabled to generate an updated manpage
+ @echo As a result, your manpage is out of date.
endif
+jq.1: jq.1.prebuilt
+ $(AM_V_GEN) cp jq.1.prebuilt $@
### Build oniguruma
diff --git a/configure.ac b/configure.ac
index bc6f096d..0441d4a2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -77,7 +77,7 @@ AC_ARG_ENABLE([gcov],
dnl Don't attempt to build docs if python deps aren't installed
AC_ARG_ENABLE([docs],
- AC_HELP_STRING([--disable-docs], [don't build docs]))
+ AC_HELP_STRING([--disable-docs], [don't build docs]), [], [enable_docs=yes])
dnl Don't attempt to build the error injection object (if there is no LD_PRELOAD support)
AC_ARG_ENABLE([error-injection],
@@ -87,28 +87,29 @@ dnl Enable building all static
AC_ARG_ENABLE([all-static],
AC_HELP_STRING([--enable-all-static], [link jq with static libraries only]))
-AS_IF([test "x$enable_docs" = "xyes"],[
- AC_CHECK_PROGS(pipenv_cmd, pipenv)
+dnl find pipenv
+AC_ARG_VAR([PIPENV], [pipenv command])
+AC_CHECK_PROGS([PIPENV], pipenv)
- AC_CACHE_CHECK([for Python dependencies], [jq_cv_python_deps],
- [jq_cv_python_deps=yes;
- AS_IF([test "x$pipenv_cmd" = "x" || \
- ! bmsg="`cd ${srcdir}/docs; LC_ALL=$LANG "$pipenv_cmd" check`"],[
- AC_MSG_ERROR([$bmsg])
- cat <<EOF
+AS_IF([test "x$enable_docs" != "xno"],[
+ AC_CACHE_CHECK([for Python dependencies], [jq_cv_python_deps],[
+ jq_cv_python_deps=yes
+ AS_IF([test "x$PIPENV" = "x" || \
+ ! bmsg="`cd ${srcdir}/docs; LC_ALL=$LANG "$PIPENV" --venv`"],[
+ jq_cv_python_deps=no
+ ])
+ ])
+
+ AS_IF([test "x$jq_cv_python_deps" != "xyes"], [
+ AC_MSG_WARN([Error checking python dependencies: $bmsg
*****************************************************************
* Python dependencies for building jq documentation not found. *
* You can still build, install and hack on jq, but the manpage *
-* will not be rebuilt and some of the tests will not run. *
+* will not be rebuilt and new manpage tests will not be run. *
* See docs/README.md for how to install the docs dependencies. *
-*****************************************************************
-EOF
- jq_cv_python_deps=no
- ])])
-
- if test "x$jq_cv_python_deps" != "xyes"; then
- enable_docs=no
- fi
+*****************************************************************])
+ enable_docs=no
+ ])
])
dnl Disable decNumber support
diff --git a/tests/man.test b/tests/man.test
new file mode 100644
index 00000000..2a49effe
--- /dev/null
+++ b/tests/man.test
@@ -0,0 +1,843 @@
+.
+"Hello, world!"
+"Hello, world!"
+
+.foo
+{"foo": 42, "bar": "less interesting data"}
+42
+
+.foo
+{"notfoo": true, "alsonotfoo": false}
+null
+
+.["foo"]
+{"foo": 42}
+42
+
+.foo?
+{"foo": 42, "bar": "less interesting data"}
+42
+
+.foo?
+{"notfoo": true, "alsonotfoo": false}
+null
+
+.["foo"]?
+{"foo": 42}
+42
+
+[.foo?]
+[1,2]
+[]
+
+.[0]
+[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]
+{"name":"JSON", "good":true}
+
+.[2]
+[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]
+null
+
+.[-2]
+[1,2,3]
+2
+
+.[2:4]
+["a","b","c","d","e"]
+["c", "d"]
+
+.[2:4]
+"abcdefghi"
+"cd"
+
+.[:3]
+["a","b","c","d","e"]
+["a", "b", "c"]
+
+.[-2:]
+["a","b","c","d","e"]
+["d", "e"]
+
+.[]
+[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]
+{"name":"JSON", "good":true}
+{"name":"XML", "good":false}
+
+.[]
+[]
+
+.[]
+{"a": 1, "b": 1}
+1
+1
+
+.foo, .bar
+{"foo": 42, "bar": "something else", "baz": true}
+42
+"something else"
+
+.user, .projects[]
+{"user":"stedolan", "projects": ["jq", "wikiflow"]}
+"stedolan"
+"jq"
+"wikiflow"
+
+.[4,2]
+["a","b","c","d","e"]
+"e"
+"c"
+
+.[] | .name
+[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]
+"JSON"
+"XML"
+
+(. + 2) * 5
+1
+15
+
+[.user, .projects[]]
+{"user":"stedolan", "projects": ["jq", "wikiflow"]}
+["stedolan", "jq", "wikiflow"]
+
+[ .[] | . * 2]
+[1, 2, 3]
+[2, 4, 6]
+
+{user, title: .titles[]}
+{"user":"stedolan","titles":["JQ Primer", "More JQ"]}
+{"user":"stedolan", "title": "JQ Primer"}
+{"user":"stedolan", "title": "More JQ"}
+
+{(.user): .titles}
+{"user":"stedolan","titles":["JQ Primer", "More JQ"]}
+{"stedolan": ["JQ Primer", "More JQ"]}
+
+..|.a?
+[[{"a":1}]]
+1
+
+.a + 1
+{"a": 7}
+8
+
+.a + .b
+{"a": [1,2], "b": [3,4]}
+[1,2,3,4]
+
+.a + null
+{"a": 1}
+1
+
+.a + 1
+{}
+1
+
+{a: 1} + {b: 2} + {c: 3} + {a: 42}
+null
+{"a": 42, "b": 2, "c": 3}
+
+4 - .a
+{"a":3}
+1
+
+. - ["xml", "yaml"]
+["xml", "yaml", "json"]
+["json"]
+
+10 / . * 3
+5
+6
+
+. / ", "
+"a, b,c,d, e"
+["a","b,c,d","e"]
+
+{"k": {"a": 1, "b": 2}} * {"k": {"a": 0,"c": 3}}
+null
+{"k": {"a": 0, "b": 2, "c": 3}}
+
+.[] | (1 / .)?
+[1,0,-1]
+1
+-1
+
+.[] | length
+[[1,2], "string", {"a":2}, null]
+2
+6
+1
+0
+
+utf8bytelength
+"\u03bc"
+2
+
+keys
+{"abc": 1, "abcd": 2, "Foo": 3}
+["Foo", "abc", "abcd"]
+
+keys
+[42,3,35]
+[0,1,2]
+
+map(has("foo"))
+[{"foo": 42}, {}]
+[true, false]
+
+map(has(2))
+[[0,1], ["a","b","c"]]
+[false, true]
+
+.[] | in({"foo": 42})
+["foo", "bar"]
+true
+false
+
+map(in([0,1]))
+[2, 0]
+[false, true]
+
+map(.+1)
+[1,2,3]
+[2,3,4]
+
+map_values(.+1)
+{"a": 1, "b": 2, "c": 3}
+{"a": 2, "b": 3, "c": 4}
+
+path(.a[0].b)
+null
+["a",0,"b"]
+
+[path(..)]
+{"a":[{"b":1}]}
+[[],["a"],["a",0],["a",0,"b"]]
+
+del(.foo)
+{"foo": 42, "bar": 9001, "baz": 42}
+{"bar": 9001, "baz": 42}
+
+del(.[1, 2])
+["foo", "bar", "baz"]
+["foo"]
+
+getpath(["a","b"])
+null
+null
+
+[getpath(["a","b"], ["a","c"])]
+{"a":{"b":0, "c":1}}
+[0, 1]
+
+setpath(["a","b"]; 1)
+null
+{"a": {"b": 1}}
+
+setpath(["a","b"]; 1)
+{"a":{"b":0}}
+{"a": {"b": 1}}
+
+setpath([0,"a"]; 1)
+null
+[{"a":1}]
+
+delpaths([["a","b"]])
+{"a":{"b":1},"x":{"y":2}}
+{"a":{},"x":{"y":2}}
+
+to_entries
+{"a": 1, "b": 2}
+[{"key":"a", "value":1}, {"key":"b", "value":2}]
+
+from_entries
+[{"key":"a", "value":1}, {"key":"b", "value":2}]
+{"a": 1, "b": 2}
+
+with_entries(.key |= "KEY_" + .)
+{"a": 1, "b": 2}
+{"KEY_a": 1, "KEY_b": 2}
+
+map(select(. >= 2))
+[1,5,3,0,7]
+[5,3,7]
+
+.[] | select(.id == "second")
+[{"id": "first", "val": 1}, {"id": "second", "val": 2}]
+{"id": "second", "val": 2}
+
+.[]|numbers
+[[],{},1,"foo",null,true,false]
+1
+
+1, empty, 2
+null
+1
+2
+
+[1,2,empty,3]
+null
+[1,2,3]
+
+try error("\($__loc__)") catch .
+null
+"{\"file\":\"<top-level>\",\"line\":1}"
+
+[paths]
+[1,[[],{"a":2}]]
+[[0],[1],[1,0],[1,1],[1,1,"a"]]
+
+[paths(scalars)]
+[1,[[],{"a":2}]]
+[[0],[1,1,"a"]]
+
+add
+["a","b","c"]
+"abc"
+
+add
+[1, 2, 3]
+6
+
+add
+[]
+null
+
+any
+[true, false]
+true
+
+any
+[false, false]
+false
+
+any
+[]
+false
+
+all
+[true, false]
+false
+
+all
+[true, true]
+true
+
+all
+[]
+true
+
+flatten
+[1, [2], [[3]]]
+[1, 2, 3]
+
+flatten(1)
+[1, [2], [[3]]]
+[1, 2, [3]]
+
+flatten
+[[]]
+[]
+
+flatten
+[{"foo": "bar"}, [{"foo": "baz"}]]
+[{"foo": "bar"}, {"foo": "baz"}]
+
+range(2;4)
+null
+2
+3
+
+[range(2;4)]
+null
+[2,3]
+
+[range(4)]
+null
+[0,1,2,3]
+
+[range(0;10;3)]
+null
+[0,3,6,9]
+
+[range(0;10;-1)]
+null
+[]
+
+[range(0;-5;-1)]
+null
+[0,-1,-2,-3,-4]
+
+floor
+3.14159
+3
+
+sqrt
+9
+3
+
+.[] | tonumber
+[1, "1"]
+1
+1
+
+.[] | tostring
+[1, "1", [1]]
+"1"
+"1"
+"[1]"
+
+map(type)
+[0, false, [], {}, null, "hello"]
+["number", "boolean", "array", "object", "null", "string"]
+
+.[] | (infinite * .) < 0
+[-1, 1]
+true
+false
+
+infinite, nan | type
+null
+"number"
+"number"
+
+sort
+[8,3,null,6]
+[null,3,6,8]
+
+sort_by(.foo)
+[{"foo":4, "bar":10}, {"foo":3, "bar":100}, {"foo":2, "bar":1}]
+[{"foo":2, "bar":1}, {"foo":3, "bar":100}, {"foo":4, "bar":10}]
+
+group_by(.foo)
+[{"foo":1, "bar":10}, {"foo":3, "bar":100}, {"foo":1, "bar":1}]
+[[{"foo":1, "bar":10}, {"foo":1, "bar":1}], [{"foo":3, "bar":100}]]
+
+min
+[5,4,2,7]
+2
+
+max_by(.foo)
+[{"foo":1, "bar":14}, {"foo":2, "bar":3}]
+{"foo":2, "bar":3}
+
+unique
+[1,2,5,3,5,3,1,3]
+[1,2,3,5]
+
+unique_by(.foo)
+[{"foo": 1, "bar": 2}, {"foo": 1, "bar": 3}, {"foo": 4, "bar": 5}]
+[{"foo": 1, "bar": 2}, {"foo": 4, "bar": 5}]
+
+unique_by(length)
+["chunky", "bacon", "kitten", "cicada", "asparagus"]
+["bacon", "chunky", "asparagus"]
+
+reverse
+[1,2,3,4]
+[4,3,2,1]
+
+contains("bar")
+"foobar"
+true
+
+contains(["baz", "bar"])
+["foobar", "foobaz", "blarp"]
+true
+
+contains(["bazzzzz", "bar"])
+["foobar", "foobaz", "blarp"]
+false
+
+contains({foo: 12, bar: [{barp: 12}]})
+{"foo": 12, "bar":[1,2,{"barp":12, "blip":13}]}
+true
+
+contains({foo: 12, bar: [{barp: 15}]})
+{"foo": 12, "bar":[1,2,{"barp":12, "blip":13}]}
+false
+
+indices(", ")
+"a,b, cd, efg, hijk"
+[3,7,12]
+
+indices(1)
+[0,1,2,1,3,1,4]
+[1,3,5]
+
+indices([1,2])
+[0,1,2,3,1,4,2,5,1,2,6,7]
+[1,8]
+
+index(", ")
+"a,b, cd, efg, hijk"
+3
+
+rindex(", ")
+"a,b, cd, efg, hijk"
+12
+
+inside("foobar")
+"bar"
+true
+
+inside(["foobar", "foobaz", "blarp"])
+["baz", "bar"]
+true
+
+inside(["foobar", "foobaz", "blarp"])
+["bazzzzz", "bar"]
+false
+
+inside({"foo": 12, "bar":[1,2,{"barp":12, "blip":13}]})
+{"foo": 12, "bar": [{"barp": 12}]}
+true
+
+inside({"foo": 12, "bar":[1,2,{"barp":12, "blip":13}]})
+{"foo": 12, "bar": [{"barp": 15}]}
+false
+
+[.[]|startswith("foo")]
+["fo", "foo", "barfoo", "foobar", "barfoob"]
+[false, true, false, true, false]
+
+[.[]|endswith("foo")]
+["foobar", "barfoo"]
+[false, true]
+
+combinations
+[[1,2], [3, 4]]
+[1, 3]
+[1, 4]
+[2, 3]
+[2, 4]
+
+combinations(2)
+[0, 1]
+[0, 0]
+[0, 1]
+[1, 0]
+[1, 1]
+
+[.[]|ltrimstr("foo")]
+["fo", "foo", "barfoo", "foobar", "afoo"]
+["fo","","barfoo","bar","afoo"]
+
+[.[]|rtrimstr("foo")]
+["fo", "foo", "barfoo", "foobar", "foob"]
+["fo","","bar","foobar","foob"]
+
+explode
+"foobar"
+[102,111,111,98,97,114]
+
+implode
+[65, 66, 67]
+"ABC"
+
+split(", ")
+"a, b,c,d, e, "
+["a","b,c,d","e",""]
+
+join(", ")
+["a","b,c,d","e"]
+"a, b,c,d, e"
+
+join(" ")
+["a",1,2.3,true,null,false]
+"a 1 2.3 true false"
+
+[while(.<100; .*2)]
+1
+[1,2,4,8,16,32,64]
+
+[.,1]|until(.[0] < 1; [.[0] - 1, .[1] * .[0]])|.[1]
+4
+24
+
+recurse(.foo[])
+{"foo":[{"foo": []}, {"foo":[{"foo":[]}]}]}
+{"foo":[{"foo":[]},{"foo":[{"foo":[]}]}]}
+{"foo":[]}
+{"foo":[{"foo":[]}]}
+{"foo":[]}
+
+recurse
+{"a":0,"b":[1]}
+{"a":0,"b":[1]}
+0
+[1]
+1
+
+recurse(. * .; . < 20)
+2
+2
+4
+16
+
+walk(if type == "array" then sort else . end)
+[[4, 1, 7], [8, 5, 2], [3, 6, 9]]
+[[1,4,7],[2,5,8],[3,6,9]]
+
+walk( if type == "object" then with_entries( .key |= sub( "^_+"; "") ) else . end )
+[ { "_a": { "__b": 2 } } ]
+[{"a":{"b":2}}]
+
+$ENV.PAGER
+null
+"less"
+
+env.PAGER
+null
+"less"
+
+transpose
+[[1], [2,3]]
+[[1,2],[null,3]]
+
+bsearch(0)
+[0,1]
+0
+
+bsearch(0)
+[1,2,3]
+-1
+
+bsearch(4) as $ix | if $ix < 0 then .[-(1+$ix)] = 4 else . end
+[1,2,3]
+[1,2,3,4]
+
+"The input was \(.), which is one less than \(.+1)"
+42
+"The input was 42, which is one less than 43"
+
+[.[]|tostring]
+[1, "foo", ["foo"]]
+["1","foo","[\"foo\"]"]
+
+[.[]|tojson]
+[1, "foo", ["foo"]]
+["1","\"foo\"","[\"foo\"]"]
+
+[.[]|tojson|fromjson]
+[1, "foo", ["foo"]]
+[1,"foo",["foo"]]
+
+@html
+"This works if x < y"
+"This works if x &lt; y"
+
+@sh "echo \(.)"
+"O'Hara's Ale"
+"echo 'O'\\''Hara'\\''s Ale'"
+
+@base64
+"This is a message"
+"VGhpcyBpcyBhIG1lc3NhZ2U="
+
+@base64d
+"VGhpcyBpcyBhIG1lc3NhZ2U="
+"This is a message"
+
+fromdate
+"2015-03-05T23:51:47Z"
+1425599507
+
+strptime("%Y-%m-%dT%H:%M:%SZ")
+"2015-03-05T23:51:47Z"
+[2015,2,5,23,51,47,4,63]
+
+strptime("%Y-%m-%dT%H:%M:%SZ")|mktime
+"2015-03-05T23:51:47Z"
+1425599507
+
+.[] == 1
+[1, 1.0, "1", "banana"]
+true
+true
+false
+false
+
+if . == 0 then "zero" elif . == 1 then "one" else "many" end
+2
+"many"
+
+. < 5
+2
+true
+
+42 and "a string"
+null
+true
+
+(true, false) or false
+null
+true
+false
+
+(true, true) and (true, false)
+null
+true
+false
+true
+false
+
+[true, false | not]
+null
+[false, true]
+
+.foo // 42
+{"foo": 19}
+19
+
+.foo // 42
+{}
+42
+
+try .a catch ". is not an object"
+true
+". is not an object"
+
+[.[]|try .a]
+[{}, true, {"a":1}]
+[null, 1]
+
+try error("some exception") catch .
+true
+"some exception"
+
+[.[]|(.a)?]
+[{}, true, {"a":1}]
+[null, 1]
+
+test("foo")
+"foo"
+true
+
+.[] | test("a b c # spaces are ignored"; "ix")
+["xabcd", "ABC"]
+true
+true
+
+match("(abc)+"; "g")
+"abc abc"
+{"offset": 0, "length": 3, "string": "abc", "captures": [{"offset": 0, "length": 3, "string": "abc", "name": null}]}
+{"offset": 4, "length": 3, "string": "abc", "captures": [{"offset": 4, "length": 3, "string": "abc", "name": null}]}
+
+match("foo")
+"foo bar foo"
+{"offset": 0, "length": 3, "string": "foo", "captures": []}
+
+match(["foo", "ig"])
+"foo bar FOO"
+{"offset": 0, "length": 3, "string": "foo", "captures": []}
+{"offset": 8, "length": 3, "string": "FOO", "captures": []}
+
+match("foo (?<bar123>bar)? foo"; "ig")
+"foo bar foo foo foo"
+{"offset": 0, "length": 11, "string": "foo bar foo", "captures": [{"offset": 4, "length": 3, "string": "bar", "name": "bar123"}]}
+{"offset": 12, "length": 8, "string": "foo foo", "captures": [{"offset": -1, "length": 0, "string": null, "name": "bar123"}]}
+
+[ match("."; "g")] | length
+"abc"
+3
+
+capture("(?<a>[a-z]+)-(?<n>[0-9]+)")
+"xyzzy-14"
+{ "a": "xyzzy", "n": "14" }
+
+.bar as $x | .foo | . + $x
+{"foo":10, "bar":200}
+210
+
+. as $i|[(.*2|. as $i| $i), $i]
+5
+[10,5]
+
+. as [$a, $b, {c: $c}] | $a + $b + $c
+[2, 3, {"c": 4, "d": 5}]
+9
+
+.[] as [$a, $b] | {a: $a, b: $b}
+[[0], [0, 1], [2, 1, 0]]
+{"a":0,"b":null}
+{"a":0,"b":1}
+{"a":2,"b":1}
+
+.[] as {$a, $b, c: {$d, $e}} ?// {$a, $b, c: [{$d, $e}]} | {$a, $b, $d, $e}
+[{"a": 1, "b": 2, "c": {"d": 3, "e": 4}}, {"a": 1, "b": 2, "c": [{"d": 3, "e": 4}]}]
+{"a":1,"b":2,"d":3,"e":4}
+{"a":1,"b":2,"d":3,"e":4}
+
+.[] as {$a, $b, c: {$d}} ?// {$a, $b, c: [{$e}]} | {$a, $b, $d, $e}
+[{"a": 1, "b": 2, "c": {"d": 3, "e": 4}}, {"a": 1, "b": 2, "c": [{"d": 3, "e": 4}]}]
+{"a":1,"b":2,"d":3,"e":null}
+{"a":1,"b":2,"d":null,"e":4}
+
+.[] as [$a] ?// [$b] | if $a != null then error("err: \($a)") else {$a,$b} end
+[[3]]
+{"a":null,"b":3}
+
+def addvalue(f): . + [f]; map(addvalue(.[0]))
+[[1,2],[10,20]]
+[[1,2,1], [10,20,10]]
+
+def addvalue(f): f as $x | map(. + $x); addvalue(.[0])
+[[1,2],[10,20]]
+[[1,2,1,2], [10,20,1,2]]
+
+reduce .[] as $item (0; . + $item)
+[10,2,5,3]
+20
+
+isempty(empty)
+null
+true
+
+[limit(3;.[])]
+[0,1,2,3,4,5,6,7,8,9]
+[0,1,2]
+
+[first(range(.)), last(range(.)), nth(./2; range(.))]
+10
+[0,9,5]
+
+[range(.)]|[first, last, nth(5)]
+10
+[0,9,5]
+
+[foreach .[] as $item ([[],[]]; if $item == null then [[],.[0]] else [(.[0] + [$item]),[]] end; if $item == null then .[1] else empty end)]
+[1,2,3,4,null,"a","b",null]
+[[1,2,3,4],["a","b"]]
+
+def range(init; upto; by): def _range: if (by > 0 and . < upto) or (by < 0 and . > upto) then ., ((.+by)|_range) else . end; if by == 0 then init else init|_range end | select((by > 0 and . < upto) or (by < 0 and . > upto)); range(0; 10; 3)
+null
+0
+3
+6
+9
+
+def while(cond; update): def _while: if cond then ., (update | _while) else empty end; _while; [while(.<100; .*2)]
+1
+[1,2,4,8,16,32,64]
+
+[1|truncate_stream([[0],1],[[1,0],2],[[1,0]],[[1]])]
+1
+[[[0],2],[[0]]]
+
+fromstream(1|truncate_stream([[0],1],[[1,0],2],[[1,0]],[[1]]))
+null
+[2]
+
+. as $dot|fromstream($dot|tostream)|.==$dot
+[0,[1,{"a":1},{"b":2}]]
+true
+
+(..|select(type=="boolean")) |= if . then 1 else 0 end
+[true,false,[5,true,[true,[false]],false]]
+[1,0,[5,1,[1,[0]],0]]
+
+.foo += 1
+{"foo": 42}
+{"foo": 43}
+
diff --git a/tests/mantest b/tests/mantest
index e86792ed..2e429eea 100755
--- a/tests/mantest
+++ b/tests/mantest
@@ -3,5 +3,4 @@
. "${0%/*}/setup" "$@"
# We set PAGER because there's a mantest for `env` that uses it.
-(cd $JQBASEDIR/docs && pipenv run python3 build_mantests.py) |
- env PAGER=less $VALGRIND $Q $JQ -L "$mods" --run-tests
+env PAGER=less $VALGRIND $Q $JQ -L "$mods" --run-tests < $JQBASEDIR/tests/man.test