diff options
Diffstat (limited to 'test')
35 files changed, 1105 insertions, 646 deletions
diff --git a/test/.clang-tidy b/test/.clang-tidy index 75f5312d..99a3264a 100644 --- a/test/.clang-tidy +++ b/test/.clang-tidy @@ -8,8 +8,14 @@ Checks: > -bugprone-easily-swappable-parameters, -cert-err33-c, -clang-analyzer-nullability.NullableDereferenced, + -clang-analyzer-optin.core.EnumCastOutOfRange, -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, -concurrency-mt-unsafe, -hicpp-signed-bitwise, - -readability-function-cognitive-complexity, + -readability-redundant-casting, +CheckOptions: + - key: readability-function-cognitive-complexity.IgnoreMacros + value: 'true' + - key: readability-function-cognitive-complexity.Threshold + value: '6' InheritParentConfig: true diff --git a/test/extra/bad/bad-bom-1.ttl b/test/extra/bad/bad-bom-1.ttl new file mode 100644 index 00000000..7d7681d4 --- /dev/null +++ b/test/extra/bad/bad-bom-1.ttl @@ -0,0 +1,3 @@ +ï# This file starts with the first byte of the UTF-8 Byte Order Mark + +<http://example.org/thing> a <http://example.org/Thing> . diff --git a/test/extra/bad/bad-bom-2.ttl b/test/extra/bad/bad-bom-2.ttl new file mode 100644 index 00000000..9ce3f093 --- /dev/null +++ b/test/extra/bad/bad-bom-2.ttl @@ -0,0 +1,3 @@ +ï»# This file starts with the first two bytes of the UTF-8 Byte Order Mark + +<http://example.org/thing> a <http://example.org/Thing> . diff --git a/test/extra/bad/bad-bom-only-1.ttl b/test/extra/bad/bad-bom-only-1.ttl new file mode 100644 index 00000000..a4a063a1 --- /dev/null +++ b/test/extra/bad/bad-bom-only-1.ttl @@ -0,0 +1 @@ +ï
\ No newline at end of file diff --git a/test/extra/bad/bad-bom-only-2.ttl b/test/extra/bad/bad-bom-only-2.ttl new file mode 100644 index 00000000..022c50f1 --- /dev/null +++ b/test/extra/bad/bad-bom-only-2.ttl @@ -0,0 +1 @@ +ï»
\ No newline at end of file diff --git a/test/extra/bad/bad-prefix-dot.ttl b/test/extra/bad/bad-prefix-dot.ttl new file mode 100644 index 00000000..7b02211f --- /dev/null +++ b/test/extra/bad/bad-prefix-dot.ttl @@ -0,0 +1 @@ +@prefix dotted.: <http://example.org/> . diff --git a/test/extra/bad/manifest.ttl b/test/extra/bad/manifest.ttl index 6b6df540..64dbf05f 100644 --- a/test/extra/bad/manifest.ttl +++ b/test/extra/bad/manifest.ttl @@ -13,7 +13,10 @@ <#bad-blank-node-label> <#bad-blank-predicate> <#bad-blank-syntax> - <#bad-bom> + <#bad-bom-1> + <#bad-bom-2> + <#bad-bom-only-1> + <#bad-bom-only-2> <#bad-char-in-local> <#bad-char-in-prefix> <#bad-char-in-uri> @@ -113,8 +116,9 @@ <#bad-object2> <#bad-paths> <#bad-pn-escape> - <#bad-prefix-missing-colon> <#bad-prefix> + <#bad-prefix-dot> + <#bad-prefix-missing-colon> <#bad-quote-in-uri> <#bad-semicolon-after-subject> <#bad-string> @@ -164,10 +168,25 @@ mf:action <bad-blank-syntax.ttl> ; mf:name "bad-blank-syntax" . -<#bad-bom> +<#bad-bom-1> + a rdft:TestTurtleNegativeSyntax ; + mf:action <bad-bom-1.ttl> ; + mf:name "bad-bom-1" . + +<#bad-bom-2> + a rdft:TestTurtleNegativeSyntax ; + mf:action <bad-bom-2.ttl> ; + mf:name "bad-bom-2" . + +<#bad-bom-only-1> + a rdft:TestTurtleNegativeSyntax ; + mf:action <bad-bom-only-1.ttl> ; + mf:name "bad-bom-only-1" . + +<#bad-bom-only-2> a rdft:TestTurtleNegativeSyntax ; - mf:action <bad-bom.ttl> ; - mf:name "bad-bom" . + mf:action <bad-bom-only-2.ttl> ; + mf:name "bad-bom-only-2" . <#bad-char-in-local> a rdft:TestTurtleNegativeSyntax ; @@ -669,6 +688,11 @@ mf:action <bad-prefix.ttl> ; mf:name "bad-prefix" . +<#bad-prefix-dot> + a rdft:TestTurtleNegativeSyntax ; + mf:action <bad-prefix-dot.ttl> ; + mf:name "bad-prefix-dot" . + <#bad-prefix-missing-colon> a rdft:TestTurtleNegativeSyntax ; mf:action <bad-prefix-missing-colon.ttl> ; diff --git a/test/extra/good/manifest.ttl b/test/extra/good/manifest.ttl index befe9451..350d7d9c 100644 --- a/test/extra/good/manifest.ttl +++ b/test/extra/good/manifest.ttl @@ -28,12 +28,15 @@ <#test-id> <#test-list-in-blank> <#test-list-subject> + <#test-local-name-ends-with-dot> + <#test-local-name-escapes> + <#test-local-name-percent> <#test-long-utf8> <#test-no-spaces> <#test-non-curie-uri> <#test-nq-syntax-all-rules> <#test-nq-syntax-dot-end> - <#test-nq-syntax-eof-after-blank-dot> + <#test-nt-syntax-all-rules> <#test-nq-syntax-eol-cr> <#test-nq-syntax-eol-crlf> <#test-nq-syntax-eol-lf> @@ -46,13 +49,14 @@ <#test-nt-syntax-eol-crlf> <#test-nt-syntax-eol-lf> <#test-nt-syntax-eol-lfcr> - <#test-out-of-range-unicode> <#test-prefix> <#test-quote-escapes> <#test-rel> <#test-semi-dot> <#test-several-eaten-dots> <#test-string-escapes> + <#test-trig-syntax-all-rules> + <#test-ttl-syntax-all-rules> <#test-uri> ) . @@ -188,6 +192,24 @@ mf:name "test-list-subject" ; mf:result <test-list-subject.nt> . +<#test-local-name-ends-with-dot> + a rdft:TestTurtleEval ; + mf:action <test-local-name-ends-with-dot.ttl> ; + mf:name "test-local-name-ends-with-dot" ; + mf:result <test-local-name-ends-with-dot.nt> . + +<#test-local-name-escapes> + a rdft:TestTurtleEval ; + mf:action <test-local-name-escapes.ttl> ; + mf:name "test-local-name-escapes" ; + mf:result <test-local-name-escapes.nt> . + +<#test-local-name-percent> + a rdft:TestTurtleEval ; + mf:action <test-local-name-percent.ttl> ; + mf:name "test-local-name-percent" ; + mf:result <test-local-name-percent.nt> . + <#test-long-utf8> a rdft:TestTurtleEval ; mf:action <test-long-utf8.ttl> ; @@ -206,11 +228,6 @@ mf:name "test-non-curie-uri" ; mf:result <test-non-curie-uri.nt> . -<#test-nq-syntax-eof-after-blank-dot> - a rdft:TestNTriplesPositiveSyntax ; - mf:action <test-nq-syntax-eof-after-blank-dot.nq> ; - mf:name "test-nq-syntax-eof-after-blank-dot" . - <#test-nq-syntax-all-rules> a rdft:TestNQuadsPositiveSyntax ; mf:action <test-nq-syntax-all-rules.nq> ; @@ -221,6 +238,11 @@ mf:action <test-nq-syntax-dot-end.nq> ; mf:name "test-nq-syntax-dot-end" . +<#test-nq-syntax-eof-after-blank-dot> + a rdft:TestNTriplesPositiveSyntax ; + mf:action <test-nq-syntax-eof-after-blank-dot.nq> ; + mf:name "test-nq-syntax-eof-after-blank-dot" . + <#test-nq-syntax-eol-cr> a rdft:TestNTriplesPositiveSyntax ; mf:action <test-nq-syntax-eol-cr.nq> ; @@ -281,12 +303,6 @@ mf:action <test-nt-syntax-eol-lfcr.nt> ; mf:name "test-nt-syntax-eol-lfcr" . -<#test-out-of-range-unicode> - a rdft:TestTurtleEval ; - mf:action <test-out-of-range-unicode.ttl> ; - mf:name "test-out-of-range-unicode" ; - mf:result <test-out-of-range-unicode.nt> . - <#test-prefix> a rdft:TestTurtleEval ; mf:action <test-prefix.ttl> ; @@ -323,6 +339,16 @@ mf:name "test-string-escapes" ; mf:result <test-string-escapes.nt> . +<#test-trig-syntax-all-rules> + a rdft:TestTrigPositiveSyntax ; + mf:action <test-trig-syntax-all-rules.trig> ; + mf:name "test-trig-syntax-all-rules" . + +<#test-ttl-syntax-all-rules> + a rdft:TestTurtlePositiveSyntax ; + mf:action <test-ttl-syntax-all-rules.ttl> ; + mf:name "test-ttl-syntax-all-rules" . + <#test-uri> a rdft:TestTurtleEval ; mf:action <test-uri.ttl> ; diff --git a/test/extra/good/test-local-name-ends-with-dot.nt b/test/extra/good/test-local-name-ends-with-dot.nt new file mode 100644 index 00000000..3285348a --- /dev/null +++ b/test/extra/good/test-local-name-ends-with-dot.nt @@ -0,0 +1 @@ +<http://example.org/eg#s> <http://example.org/eg#p> <http://example.org/eg#foo.> . diff --git a/test/extra/good/test-local-name-escapes.nt b/test/extra/good/test-local-name-escapes.nt new file mode 100644 index 00000000..a6362d7a --- /dev/null +++ b/test/extra/good/test-local-name-escapes.nt @@ -0,0 +1,17 @@ +<http://example.org/s> <http://example.org/p> <http://example.org/o'> . +<http://example.org/s> <http://example.org/p> <http://example.org/o!> . +<http://example.org/s> <http://example.org/p> <http://example.org/o#> . +<http://example.org/s> <http://example.org/p> <http://example.org/o$> . +<http://example.org/s> <http://example.org/p> <http://example.org/o%> . +<http://example.org/s> <http://example.org/p> <http://example.org/o&> . +<http://example.org/s> <http://example.org/p> <http://example.org/o(> . +<http://example.org/s> <http://example.org/p> <http://example.org/o)> . +<http://example.org/s> <http://example.org/p> <http://example.org/o*> . +<http://example.org/s> <http://example.org/p> <http://example.org/o+> . +<http://example.org/s> <http://example.org/p> <http://example.org/o,> . +<http://example.org/s> <http://example.org/p> <http://example.org/o/> . +<http://example.org/s> <http://example.org/p> <http://example.org/o;> . +<http://example.org/s> <http://example.org/p> <http://example.org/o=> . +<http://example.org/s> <http://example.org/p> <http://example.org/o?> . +<http://example.org/s> <http://example.org/p> <http://example.org/o@> . +<http://example.org/s> <http://example.org/p> <http://example.org/o~> . diff --git a/test/extra/good/test-local-name-escapes.ttl b/test/extra/good/test-local-name-escapes.ttl new file mode 100644 index 00000000..8c5fce37 --- /dev/null +++ b/test/extra/good/test-local-name-escapes.ttl @@ -0,0 +1,19 @@ +@prefix eg: <http://example.org/> . + +eg:s eg:p eg:o\' . +eg:s eg:p eg:o\! . +eg:s eg:p eg:o\# . +eg:s eg:p eg:o\$ . +eg:s eg:p eg:o\% . +eg:s eg:p eg:o\& . +eg:s eg:p eg:o\( . +eg:s eg:p eg:o\) . +eg:s eg:p eg:o\* . +eg:s eg:p eg:o\+ . +eg:s eg:p eg:o\, . +eg:s eg:p eg:o\/ . +eg:s eg:p eg:o\; . +eg:s eg:p eg:o\= . +eg:s eg:p eg:o\? . +eg:s eg:p eg:o\@ . +eg:s eg:p eg:o\~ . diff --git a/test/extra/good/test-local-name-percent.nt b/test/extra/good/test-local-name-percent.nt new file mode 100644 index 00000000..e6330547 --- /dev/null +++ b/test/extra/good/test-local-name-percent.nt @@ -0,0 +1,2 @@ +<http://example.org/s> <http://example.org/p> <http://example.org/o%3E> . +<http://example.org/s> <http://example.org/p> <http://example.org/o%3f> . diff --git a/test/extra/good/test-local-name-percent.ttl b/test/extra/good/test-local-name-percent.ttl new file mode 100644 index 00000000..32fb63d5 --- /dev/null +++ b/test/extra/good/test-local-name-percent.ttl @@ -0,0 +1,4 @@ +@prefix eg: <http://example.org/> . + +eg:s eg:p eg:o%3E . +eg:s eg:p eg:o%3f . diff --git a/test/extra/lax/manifest.ttl b/test/extra/lax/manifest.ttl index b9890e14..f79e1984 100644 --- a/test/extra/lax/manifest.ttl +++ b/test/extra/lax/manifest.ttl @@ -8,14 +8,18 @@ mf:entries ( <#test-bad-string-nq> <#test-bad-string-nt> + <#test-bad-string-trig> <#test-bad-string-ttl> <#test-bad-uri-nq> <#test-bad-uri-nt> <#test-bad-uri-ttl> + <#test-bad-uri-trig> <#test-bad-utf8-nq> <#test-bad-utf8-nt> <#test-bad-utf8-ttl> + <#test-bad-utf8-trig> <#test-lone-list> + <#test-out-of-range-unicode> ) . <#test-bad-string-nq> @@ -36,6 +40,12 @@ mf:name "test-bad-string-ttl" ; mf:result <test-bad-string-out.nt> . +<#test-bad-string-trig> + a rdft:TestTrigNegativeSyntax ; + mf:action <test-bad-string.trig> ; + mf:name "test-bad-string-trig" ; + mf:result <test-bad-string-out.nt> . + <#test-bad-uri-nq> a rdft:TestNQuadsNegativeSyntax ; mf:action <test-bad-uri.nq> ; @@ -54,6 +64,12 @@ mf:name "test-bad-uri-ttl" ; mf:result <test-bad-uri-out.nt> . +<#test-bad-uri-trig> + a rdft:TestTurtleNegativeSyntax ; + mf:action <test-bad-uri.trig> ; + mf:name "test-bad-uri-trig" ; + mf:result <test-bad-uri-nq-out.nq> . + <#test-bad-utf8-nq> a rdft:TestNQuadsNegativeSyntax ; mf:action <test-bad-utf8.nq> ; @@ -72,8 +88,20 @@ mf:name "test-bad-utf8-ttl" ; mf:result <test-bad-utf8-ttl-out.nt> . +<#test-bad-utf8-trig> + a rdft:TestTurtleNegativeSyntax ; + mf:action <test-bad-utf8.trig> ; + mf:name "test-bad-utf8-trig" ; + mf:result <test-bad-utf8-ttl-out.nt> . + <#test-lone-list> a rdft:TestTurtleNegativeSyntax ; mf:action <test-lone-list.ttl> ; mf:name "test-lone-list" ; mf:result <test-lone-list.nt> . + +<#test-out-of-range-unicode> + a rdft:TestTurtleNegativeSyntax ; + mf:action <test-out-of-range-unicode.ttl> ; + mf:name "test-out-of-range-unicode" ; + mf:result <test-out-of-range-unicode.nt> . diff --git a/test/extra/lax/test-bad-string.trig b/test/extra/lax/test-bad-string.trig new file mode 100644 index 00000000..72eb9621 --- /dev/null +++ b/test/extra/lax/test-bad-string.trig @@ -0,0 +1,3 @@ +<http://example.org/s1> <http://example.org/p1> "Truncated line +<http://example.org/s1> <http://example.org/p1> "Bad escape \? " . +<http://example.org/s1> <http://example.org/p2> "Good" . diff --git a/test/extra/lax/test-bad-uri.trig b/test/extra/lax/test-bad-uri.trig new file mode 100644 index 00000000..ba852fef --- /dev/null +++ b/test/extra/lax/test-bad-uri.trig @@ -0,0 +1,8 @@ +<http://example.org/ÿÿbadg1> { + <http://example.org/s> <http://example.org/p> <http://example.org/goodo1> +} + +<http://example.org/s> + <http://example.org/p> <http://example.org/ bado1> ; + <http://example.org/p> <http://example.org/ÿÿbado2> ; + <http://example.org/p> <http://example.org/goodo2> . diff --git a/test/extra/lax/test-bad-utf8.trig b/test/extra/lax/test-bad-utf8.trig new file mode 100644 index 00000000..0e177366 --- /dev/null +++ b/test/extra/lax/test-bad-utf8.trig @@ -0,0 +1,6 @@ +<http://example.org/s> <http://example.org/p> "Impossible bytes: þ ÿ" . +<http://example.org/s> <http://example.org/p> "2 continuation bytes: €¿" . +<http://example.org/s> <http://example.org/p> "Missing continuation: À" . +<http://example.org/s> <http://example.org/p> """Impossible bytes: þ ÿ""" . +<http://example.org/s> <http://example.org/p> """2 continuation bytes: €¿""" . +<http://example.org/s> <http://example.org/p> """Missing continuation: À""" . diff --git a/test/extra/good/test-out-of-range-unicode.nt b/test/extra/lax/test-out-of-range-unicode.nt index 5def9e31..5def9e31 100644 --- a/test/extra/good/test-out-of-range-unicode.nt +++ b/test/extra/lax/test-out-of-range-unicode.nt diff --git a/test/extra/good/test-out-of-range-unicode.ttl b/test/extra/lax/test-out-of-range-unicode.ttl index 7e64785a..7e64785a 100644 --- a/test/extra/good/test-out-of-range-unicode.ttl +++ b/test/extra/lax/test-out-of-range-unicode.ttl diff --git a/test/extra/pretty/datatypes.ttl b/test/extra/pretty/datatypes.ttl index 721dfe4d..8f8b13f2 100644 --- a/test/extra/pretty/datatypes.ttl +++ b/test/extra/pretty/datatypes.ttl @@ -9,6 +9,8 @@ eg:s 1 , 2.3 , "4."^^xsd:decimal , + "5"^^xsd:decimal , + "6.7E8"^^xsd:float , false , true , "x"^^eg:datatype , diff --git a/test/extra/pretty/named-graph.trig b/test/extra/pretty/named-graph.trig index 5cd12f3b..29f1d970 100644 --- a/test/extra/pretty/named-graph.trig +++ b/test/extra/pretty/named-graph.trig @@ -1,8 +1,18 @@ @prefix eg: <http://example.org/> . -eg:g { - eg:s - eg:p [ +eg:g1 { + eg:s1 + eg:p1 [ a eg:Object ] . } + +eg:g2 { + eg:s2 + eg:p2 [ + a eg:Object + ] . +} + +eg:s3 + eg:p3 eg:o3 . diff --git a/test/headers/.clang-tidy b/test/headers/.clang-tidy index eaf6ac95..06ede334 100644 --- a/test/headers/.clang-tidy +++ b/test/headers/.clang-tidy @@ -1,4 +1,4 @@ -# Copyright 2020-2022 David Robillard <d@drobilla.net> +# Copyright 2020-2024 David Robillard <d@drobilla.net> # SPDX-License-Identifier: 0BSD OR ISC Checks: > @@ -6,6 +6,9 @@ Checks: > -altera-*, -llvmlibc-*, -readability-identifier-length, +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: '0' WarningsAsErrors: '*' HeaderFilterRegex: '.*' FormatStyle: file diff --git a/test/headers/test_headers.c b/test/headers/test_headers.c index c855c103..62be0976 100644 --- a/test/headers/test_headers.c +++ b/test/headers/test_headers.c @@ -3,8 +3,7 @@ #include "serd/serd.h" // IWYU pragma: keep -SERD_CONST_FUNC -int +SERD_CONST_FUNC int main(void) { return 0; diff --git a/test/meson.build b/test/meson.build index f6f56c2b..4d770304 100644 --- a/test/meson.build +++ b/test/meson.build @@ -4,110 +4,6 @@ run_suite = find_program('run_suite.py') wrapper = meson.get_external_property('exe_wrapper', '') -######################## -# Scripts and Metadata # -######################## - -plot_script_paths = [ - '../scripts/serd_bench.py', -] - -simple_script_paths = [ - '../scripts/check_formatting.py', - 'serd_test_util/__init__.py', - 'run_suite.py', - 'test_quiet.py', - 'test_stdin.py', - 'test_write_error.py', -] - -ttl_metadata_file_paths = [ - '../serd.ttl', - 'extra/abbreviate/manifest.ttl', - 'extra/bad/manifest.ttl', - 'extra/big/manifest.ttl', - 'extra/full/manifest.ttl', - 'extra/good/manifest.ttl', - 'extra/lax/manifest.ttl', - 'extra/perfect/manifest.ttl', - 'extra/prefix/manifest.ttl', - 'extra/pretty/manifest.ttl', - 'extra/qualify/manifest.ttl', - 'extra/root/manifest.ttl', -] - -plot_scripts = files(plot_script_paths) -simple_scripts = files(simple_script_paths) -python_script_paths = simple_script_paths + plot_script_paths -python_scripts = plot_scripts + simple_scripts - -if get_option('lint') - # Check release metadata - if not meson.is_subproject() - autoship = find_program('autoship', required: false) - if autoship.found() - test('autoship', autoship, args: ['test', serd_src_root], suite: 'data') - endif - endif - - # Check licensing metadata - reuse = find_program('reuse', required: false) - if reuse.found() - test( - 'REUSE', - reuse, - args: ['--root', serd_src_root, 'lint'], - suite: 'data', - ) - endif - - # Check script formatting - black = find_program('black', required: false) - if black.found() - black_opts = ['--check', '-q', '-l', '79'] - foreach script_path : python_script_paths - script = files(script_path) - name = script_path.underscorify() - test(name, black, args: black_opts + [script], suite: 'scripts') - endforeach - endif - - # Check scripts for errors with flake8 - flake8 = find_program('flake8', required: false) - if flake8.found() - test('flake8', flake8, args: python_scripts, suite: 'scripts') - endif - - # Check scripts for errors with pylint - pylint = find_program('pylint', required: false) - if pylint.found() - pymod = import('python') - plot_py = pymod.find_installation( - 'python3', - modules: ['matplotlib'], - required: false, - ) - - pylint_scripts = simple_scripts - if plot_py.found() - pylint_scripts += plot_scripts - endif - - pylint_args = ['--disable', 'bad-option-value'] - test('pylint', pylint, args: pylint_args + pylint_scripts, suite: 'scripts') - endif - - # Check Turtle formatting with serdi - foreach ttl_file_path : ttl_metadata_file_paths - test( - ttl_file_path.underscorify(), - check_formatting_py, - args: [files(ttl_file_path), serdi, '-o', 'turtle'], - suite: 'data', - ) - endforeach -endif - ################### # Header Warnings # ################### @@ -118,22 +14,27 @@ subdir('headers') # Unit Tests # ############## -unit_tests = [ +unit_test_names = [ 'env', 'free_null', 'node', + 'reader', 'reader_writer', 'string', 'uri', 'writer', ] -foreach unit : unit_tests +unit_test_sources = files('headers/test_headers.c') + +foreach name : unit_test_names + source = files('test_@0@.c'.format(name)) + unit_test_sources += source test( - unit, + name, executable( - 'test_@0@'.format(unit), - files('test_@0@.c'.format(unit)), + 'test_@0@'.format(name), + source, c_args: c_suppressions, dependencies: serd_dep, ), @@ -154,14 +55,20 @@ simple_command_tests = { 'serdi': { 'bad': [ ['-c'], + ['-cx'], ['-fi'], ['-i', 'turtle'], - ['-i', 'unknown'], + ['-i', 'turt'], ['-i'], - ['-o', 'unknown'], + ['-ix'], + ['-o', '~unknown'], + ['-o', 'ntripleses'], ['-o'], + ['-ox'], ['-p'], + ['-px'], ['-r'], + ['-rx'], ['-z'], ], 'good': [ @@ -178,6 +85,7 @@ if is_variable('serdi') script_args = common_script_args + ['--serdi', serdi] serd_ttl = files('../serd.ttl')[0] bad_input_file = files('extra/bad/bad-base.ttl') + text_input_file = files('extra/bad/README.md') test('serd_ttl', serdi, args: [serd_ttl], suite: 'data') @@ -214,6 +122,7 @@ if is_variable('serdi') 'string': ['-s', '<foo> a <Bar> .'], 'no_such_file': ['no_such_file'], 'remote': ['ftp://example.org/unsupported.ttl'], + 'text': [text_input_file], } foreach name, args : bad_input_tests @@ -384,3 +293,138 @@ if is_variable('serdi') ) endforeach endif + +######## +# Lint # +######## + +plot_script_paths = [ + '../scripts/serd_bench.py', +] + +simple_script_paths = [ + '../scripts/check_formatting.py', + 'serd_test_util/__init__.py', + 'run_suite.py', + 'test_quiet.py', + 'test_stdin.py', + 'test_write_error.py', +] + +ttl_metadata_file_paths = [ + '../serd.ttl', + 'extra/abbreviate/manifest.ttl', + 'extra/bad/manifest.ttl', + 'extra/big/manifest.ttl', + 'extra/full/manifest.ttl', + 'extra/good/manifest.ttl', + 'extra/lax/manifest.ttl', + 'extra/perfect/manifest.ttl', + 'extra/prefix/manifest.ttl', + 'extra/pretty/manifest.ttl', + 'extra/qualify/manifest.ttl', + 'extra/root/manifest.ttl', +] + +plot_scripts = files(plot_script_paths) +simple_scripts = files(simple_script_paths) +python_script_paths = simple_script_paths + plot_script_paths +python_scripts = plot_scripts + simple_scripts + +if get_option('lint') + all_sources = sources + unit_test_sources + files('../src/serdi.c') + + if not meson.is_subproject() + # Check release metadata + autoship = find_program('autoship', required: false) + if autoship.found() + test('autoship', autoship, args: ['test', serd_src_root], suite: 'data') + endif + + # Check code with cppcheck + cppcheck = find_program('cppcheck', required: false) + if cppcheck.found() + compdb_path = join_paths(serd_build_root, 'compile_commands.json') + suppress_path = join_paths(serd_src_root, '.suppress.cppcheck') + test( + 'cppcheck', + cppcheck, + args: [ + '--enable=warning,style,performance,portability', + '--error-exitcode=1', + '--project=' + compdb_path, + '--suppressions-list=' + suppress_path, + '-q', + ], + suite: 'code', + ) + endif + endif + + # Check licensing metadata + reuse = find_program('reuse', required: false) + if reuse.found() + test( + 'REUSE', + reuse, + args: ['--root', serd_src_root, 'lint'], + suite: 'data', + ) + endif + + # Check code formatting + clang_format = find_program('clang-format', required: false) + if clang_format.found() + test( + 'format', + clang_format, + args: ['--Werror', '--dry-run'] + c_headers + all_sources, + suite: 'code', + ) + endif + + # Check script formatting + black = find_program('black', required: false) + if black.found() + black_opts = ['--check', '-q', '-l', '79'] + foreach script_path : python_script_paths + script = files(script_path) + name = script_path.underscorify() + test(name, black, args: black_opts + [script], suite: 'scripts') + endforeach + endif + + # Check scripts for errors with flake8 + flake8 = find_program('flake8', required: false) + if flake8.found() + test('flake8', flake8, args: python_scripts, suite: 'scripts') + endif + + # Check scripts for errors with pylint + pylint = find_program('pylint', required: false) + if pylint.found() + pymod = import('python') + plot_py = pymod.find_installation( + 'python3', + modules: ['matplotlib'], + required: false, + ) + + pylint_args = ['--disable', 'bad-option-value'] + simple_scripts + if plot_py.found() + pylint_args += plot_scripts + endif + + test('pylint', pylint, args: pylint_args, suite: 'scripts') + endif + + # Check Turtle formatting with serdi + foreach ttl_file_path : ttl_metadata_file_paths + test( + ttl_file_path.underscorify(), + check_formatting_py, + args: [files(ttl_file_path), serdi, '-o', 'turtle'], + suite: 'data', + ) + endforeach +endif diff --git a/test/serd_test_util/__init__.py b/test/serd_test_util/__init__.py index 8027462b..ad417762 100644 --- a/test/serd_test_util/__init__.py +++ b/test/serd_test_util/__init__.py @@ -8,10 +8,12 @@ # pylint: disable=consider-using-f-string # pylint: disable=invalid-name +import argparse import datetime import difflib import os import re +import shlex import subprocess import sys import urllib.parse @@ -51,6 +53,33 @@ def error(message): sys.stderr.write("\n") +def wrapper_args(description, with_input=False): + """Return the command line arguments for a wrapped test.""" + + parser = argparse.ArgumentParser(description) + parser.add_argument("--serdi", default="./serdi", help="serdi executable") + parser.add_argument("--wrapper", default="", help="executable wrapper") + if with_input: + parser.add_argument("input", help="input file") + + return parser.parse_args(sys.argv[1:]) + + +def command_output(wrapper, command, stdin=None): + """Run a command and check that stdout matches the expected output.""" + + proc = subprocess.run( + shlex.split(wrapper) + command, + capture_output=True, + check=True, + encoding="utf-8", + input=stdin, + ) + + assert wrapper or not proc.stderr + return proc.stdout + + def print_result_summary(results): """Print test result summary to stdout or stderr as appropriate.""" diff --git a/test/test_env.c b/test/test_env.c index 1de075f3..903ae3f2 100644 --- a/test/test_env.c +++ b/test/test_env.c @@ -9,10 +9,13 @@ #include <stdint.h> #include <string.h> +#define NS_EG "http://example.org/" #define USTR(s) ((const uint8_t*)(s)) static SerdStatus -count_prefixes(void* handle, const SerdNode* name, const SerdNode* uri) +count_prefixes(void* const handle, + const SerdNode* const name, + const SerdNode* const uri) { (void)name; (void)uri; @@ -24,12 +27,11 @@ count_prefixes(void* handle, const SerdNode* name, const SerdNode* uri) static void test_env(void) { - SerdNode u = serd_node_from_string(SERD_URI, USTR("http://example.org/foo")); - SerdNode b = serd_node_from_string(SERD_CURIE, USTR("invalid")); - SerdNode c = serd_node_from_string(SERD_CURIE, USTR("eg.2:b")); + SerdNode u = serd_node_from_string(SERD_URI, USTR(NS_EG "foo")); + SerdNode b = serd_node_from_string(SERD_CURIE, USTR("invalid")); + SerdNode c = serd_node_from_string(SERD_CURIE, USTR("eg.2:b")); SerdEnv* env = serd_env_new(NULL); - serd_env_set_prefix_from_strings( - env, USTR("eg.2"), USTR("http://example.org/")); + serd_env_set_prefix_from_strings(env, USTR("eg.2"), USTR(NS_EG "")); assert(!serd_env_set_base_uri(env, NULL)); assert(serd_env_set_base_uri(env, &SERD_NODE_NULL)); @@ -39,7 +41,8 @@ test_env(void) SerdChunk suffix; assert(!serd_env_qualify(NULL, &u, &u, &suffix)); assert(serd_env_expand(NULL, &c, &prefix, &suffix)); - assert(serd_env_expand(env, &b, &prefix, &suffix)); + assert(serd_env_expand(env, &b, &prefix, &suffix) == SERD_ERR_BAD_ARG); + assert(serd_env_expand(env, &u, &prefix, &suffix) == SERD_ERR_BAD_ARG); SerdNode nxnode = serd_env_expand_node(NULL, &c); assert(serd_node_equals(&nxnode, &SERD_NODE_NULL)); @@ -48,7 +51,7 @@ test_env(void) assert(serd_node_equals(&xnode, &SERD_NODE_NULL)); SerdNode xu = serd_env_expand_node(env, &u); - assert(!strcmp((const char*)xu.buf, "http://example.org/foo")); + assert(!strcmp((const char*)xu.buf, NS_EG "foo")); serd_node_free(&xu); SerdNode badpre = serd_node_from_string(SERD_CURIE, USTR("hm:what")); @@ -56,7 +59,7 @@ test_env(void) assert(serd_node_equals(&xbadpre, &SERD_NODE_NULL)); SerdNode xc = serd_env_expand_node(env, &c); - assert(!strcmp((const char*)xc.buf, "http://example.org/b")); + assert(!strcmp((const char*)xc.buf, NS_EG "b")); serd_node_free(&xc); assert(serd_env_set_prefix(env, &SERD_NODE_NULL, &SERD_NODE_NULL)); @@ -71,8 +74,7 @@ test_env(void) assert(serd_node_equals(&xblank, &SERD_NODE_NULL)); int n_prefixes = 0; - serd_env_set_prefix_from_strings( - env, USTR("eg.2"), USTR("http://example.org/")); + serd_env_set_prefix_from_strings(env, USTR("eg.2"), USTR(NS_EG)); serd_env_foreach(env, count_prefixes, &n_prefixes); assert(n_prefixes == 1); diff --git a/test/test_node.c b/test/test_node.c index f08363cb..28db00ea 100644 --- a/test/test_node.c +++ b/test/test_node.c @@ -24,7 +24,7 @@ #endif static void -test_strtod(double dbl, double max_delta) +check_strtod(const double dbl, const double max_delta) { char buf[1024]; snprintf(buf, sizeof(buf), "%f", dbl); @@ -42,15 +42,21 @@ test_string_to_double(void) const double expt_test_nums[] = { 2.0E18, -5e19, +8e20, 2e+22, -5e-5, 8e0, 9e-0, 2e+0}; - const char* expt_test_strs[] = { - "02e18", "-5e019", "+8e20", "2E+22", "-5E-5", "8E0", "9e-0", " 2e+0"}; + const char* expt_test_strs[] = {"02e18", + "-5e019", + " +8e20", + "\f2E+22", + "\n-5E-5", + "\r8E0", + "\t9e-0", + "\v2e+0"}; for (size_t i = 0; i < sizeof(expt_test_nums) / sizeof(double); ++i) { const double num = serd_strtod(expt_test_strs[i], NULL); const double delta = fabs(num - expt_test_nums[i]); assert(delta <= DBL_EPSILON); - test_strtod(expt_test_nums[i], DBL_EPSILON); + check_strtod(expt_test_nums[i], DBL_EPSILON); } } @@ -144,6 +150,58 @@ test_blob_to_node(void) } static void +test_base64_decode(void) +{ + static const char* const decoded = "test"; + static const size_t decoded_len = 4U; + + // Test decoding clean base64 + { + static const char* const encoded = "dGVzdA=="; + static const size_t encoded_len = 8U; + + size_t size = 0U; + void* const data = + serd_base64_decode((const uint8_t*)encoded, encoded_len, &size); + + assert(data); + assert(size == decoded_len); + assert(!strncmp((const char*)data, decoded, decoded_len)); + serd_free(data); + } + + // Test decoding equivalent dirty base64 with ignored junk characters + { + static const char* const encoded = "d-G#V!z*d(A$%=="; + static const size_t encoded_len = 13U; + + size_t size = 0U; + void* const data = + serd_base64_decode((const uint8_t*)encoded, encoded_len, &size); + + assert(data); + assert(size == decoded_len); + assert(!strncmp((const char*)data, decoded, decoded_len)); + serd_free(data); + } + + // Test decoding effectively nothing + { + static const char* const encoded = "@#$%"; + static const size_t encoded_len = 4U; + + size_t size = 0U; + void* const data = + serd_base64_decode((const uint8_t*)encoded, encoded_len, &size); + + assert(data); + assert(!size); + // Contents of data are undefined + serd_free(data); + } +} + +static void test_node_equals(void) { const uint8_t replacement_char_str[] = {0xEF, 0xBF, 0xBD, 0}; @@ -176,6 +234,8 @@ test_node_from_string(void) static void test_node_from_substring(void) { + static const uint8_t utf8_str[] = {'l', 0xC3, 0xB6, 'n', 'g', 0}; + SerdNode empty = serd_node_from_substring(SERD_LITERAL, NULL, 32); assert(!empty.buf && !empty.n_bytes && !empty.n_chars && !empty.flags && !empty.type); @@ -187,6 +247,30 @@ test_node_from_substring(void) a_b = serd_node_from_substring(SERD_LITERAL, USTR("a\"bc"), 10); assert(a_b.n_bytes == 4 && a_b.n_chars == 4 && a_b.flags == SERD_HAS_QUOTE && !strncmp((const char*)a_b.buf, "a\"bc", 4)); + + SerdNode utf8 = serd_node_from_substring(SERD_LITERAL, utf8_str, 5); + assert(utf8.n_bytes == 5 && utf8.n_chars == 4 && !utf8.flags && + !strncmp((const char*)utf8.buf, (const char*)utf8_str, 6)); +} + +static void +test_uri_node_from_node(void) +{ + const SerdNode string = serd_node_from_string(SERD_LITERAL, USTR("s")); + SerdNode string_node = serd_node_new_uri_from_node(&string, NULL, NULL); + assert(!string_node.n_bytes); + serd_node_free(&string_node); + + const SerdNode nouri = {NULL, 0U, 0U, 0U, SERD_URI}; + SerdNode nouri_node = serd_node_new_uri_from_node(&nouri, NULL, NULL); + assert(!nouri_node.n_bytes); + serd_node_free(&nouri_node); + + const SerdNode uri = + serd_node_from_string(SERD_URI, USTR("http://example.org/p")); + SerdNode uri_node = serd_node_new_uri_from_node(&uri, NULL, NULL); + assert(uri_node.n_bytes == 20U); + serd_node_free(&uri_node); } int @@ -196,10 +280,10 @@ main(void) test_double_to_node(); test_integer_to_node(); test_blob_to_node(); + test_base64_decode(); test_node_equals(); test_node_from_string(); test_node_from_substring(); - - printf("Success\n"); + test_uri_node_from_node(); return 0; } diff --git a/test/test_quiet.py b/test/test_quiet.py index 42d05785..676284bb 100755 --- a/test/test_quiet.py +++ b/test/test_quiet.py @@ -3,20 +3,14 @@ # Copyright 2022 David Robillard <d@drobilla.net> # SPDX-License-Identifier: ISC -"""Test serdi quiet option.""" +"""Test quiet command-line option.""" -import argparse -import sys import shlex import subprocess -parser = argparse.ArgumentParser(description=__doc__) +import serd_test_util as util -parser.add_argument("--serdi", default="./serdi", help="path to serdi") -parser.add_argument("--wrapper", default="", help="executable wrapper") -parser.add_argument("input", help="invalid input file") - -args = parser.parse_args(sys.argv[1:]) +args = util.wrapper_args(__doc__, True) command = shlex.split(args.wrapper) + [args.serdi, "-q", args.input] proc = subprocess.run( command, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE diff --git a/test/test_reader.c b/test/test_reader.c new file mode 100644 index 00000000..e185b915 --- /dev/null +++ b/test/test_reader.c @@ -0,0 +1,425 @@ +// Copyright 2011-2024 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#undef NDEBUG + +#include "serd/serd.h" + +#ifdef _WIN32 +# include <windows.h> +#endif + +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define USTR(s) ((const uint8_t*)(s)) + +typedef struct { + int n_base; + int n_prefix; + int n_statement; + int n_end; +} ReaderTest; + +static SerdStatus +base_sink(void* const handle, const SerdNode* const uri) +{ + (void)uri; + + ReaderTest* const rt = (ReaderTest*)handle; + ++rt->n_base; + return SERD_SUCCESS; +} + +static SerdStatus +prefix_sink(void* const handle, + const SerdNode* const name, + const SerdNode* const uri) +{ + (void)name; + (void)uri; + + ReaderTest* const rt = (ReaderTest*)handle; + ++rt->n_prefix; + return SERD_SUCCESS; +} + +static SerdStatus +statement_sink(void* const handle, + SerdStatementFlags flags, + const SerdNode* const graph, + const SerdNode* const subject, + const SerdNode* const predicate, + const SerdNode* const object, + const SerdNode* const object_datatype, + const SerdNode* const object_lang) +{ + (void)flags; + (void)graph; + (void)subject; + (void)predicate; + (void)object; + (void)object_datatype; + (void)object_lang; + + ReaderTest* const rt = (ReaderTest*)handle; + ++rt->n_statement; + return SERD_SUCCESS; +} + +static SerdStatus +end_sink(void* const handle, const SerdNode* const node) +{ + (void)node; + + ReaderTest* const rt = (ReaderTest*)handle; + ++rt->n_end; + return SERD_SUCCESS; +} + +static void +test_read_string(void) +{ + ReaderTest rt = {0, 0, 0, 0}; + SerdReader* const reader = serd_reader_new( + SERD_TURTLE, &rt, NULL, base_sink, prefix_sink, statement_sink, end_sink); + + assert(reader); + assert(serd_reader_get_handle(reader) == &rt); + + // Test reading a string that ends exactly at the end of input (no newline) + const SerdStatus st = serd_reader_read_string( + reader, + USTR("<http://example.org/s> <http://example.org/p> " + "<http://example.org/o> .")); + + assert(!st); + assert(rt.n_base == 0); + assert(rt.n_prefix == 0); + assert(rt.n_statement == 1); + assert(rt.n_end == 0); + + serd_reader_free(reader); +} + +/// Reads a null byte after a statement, then succeeds again (like a socket) +static size_t +eof_test_read(void* const buf, + const size_t size, + const size_t nmemb, + void* const stream) +{ + assert(size == 1); + assert(nmemb == 1); + (void)size; + + static const char* const string = "_:s1 <http://example.org/p> _:o1 .\n" + "_:s2 <http://example.org/p> _:o2 .\n"; + + size_t* const count = (size_t*)stream; + + // Normal reading for the first statement + if (*count < 35) { + *(char*)buf = string[*count]; + ++*count; + return nmemb; + } + + // EOF for the first read at the start of the second statement + if (*count == 35) { + assert(string[*count] == '_'); + ++*count; + return 0; + } + + if (*count >= strlen(string)) { + return 0; + } + + // Normal reading after the EOF, adjusting for the skipped index 35 + *(char*)buf = string[*count - 1]; + ++*count; + return nmemb; +} + +static int +eof_test_error(void* const stream) +{ + (void)stream; + return 0; +} + +/// A read of a file stream hits EOF then fails to read chunks immediately +static void +test_read_eof_file(const char* const path) +{ + FILE* const f = fopen(path, "w+b"); + assert(f); + + fprintf(f, "_:s <http://example.org/p> _:o .\n"); + fflush(f); + fseek(f, 0L, SEEK_SET); + + ReaderTest rt = {0, 0, 0, 0}; + SerdReader* const reader = serd_reader_new( + SERD_TURTLE, &rt, NULL, base_sink, prefix_sink, statement_sink, end_sink); + + fseek(f, 0L, SEEK_SET); + serd_reader_start_stream(reader, f, (const uint8_t*)"test", true); + assert(serd_reader_read_chunk(reader) == SERD_SUCCESS); + assert(serd_reader_read_chunk(reader) == SERD_FAILURE); + assert(serd_reader_read_chunk(reader) == SERD_FAILURE); + serd_reader_end_stream(reader); + + fseek(f, 0L, SEEK_SET); + serd_reader_start_stream(reader, f, (const uint8_t*)"test", false); + assert(serd_reader_read_chunk(reader) == SERD_SUCCESS); + assert(serd_reader_read_chunk(reader) == SERD_FAILURE); + assert(serd_reader_read_chunk(reader) == SERD_FAILURE); + serd_reader_end_stream(reader); + + serd_reader_free(reader); + fclose(f); +} + +// A byte-wise reader hits EOF once then continues (like a socket) +static void +test_read_eof_by_byte(void) +{ + ReaderTest rt = {0, 0, 0, 0}; + SerdReader* const reader = serd_reader_new( + SERD_TURTLE, &rt, NULL, base_sink, prefix_sink, statement_sink, end_sink); + + size_t n_reads = 0U; + serd_reader_start_source_stream(reader, + eof_test_read, + eof_test_error, + &n_reads, + (const uint8_t*)"test", + 1U); + + assert(serd_reader_read_chunk(reader) == SERD_SUCCESS); + assert(serd_reader_read_chunk(reader) == SERD_FAILURE); + assert(serd_reader_read_chunk(reader) == SERD_SUCCESS); + assert(serd_reader_read_chunk(reader) == SERD_FAILURE); + + serd_reader_end_stream(reader); + serd_reader_free(reader); +} + +static void +test_read_nquads_chunks(const char* const path) +{ + static const char null = 0; + + FILE* const f = fopen(path, "w+b"); + + // Write two statements, a null separator, then another statement + + fprintf(f, + "<http://example.org/s> <http://example.org/p1> " + "<http://example.org/o1> .\n"); + + fprintf(f, + "<http://example.org/s> <http://example.org/p2> " + "<http://example.org/o2> .\n"); + + fwrite(&null, sizeof(null), 1, f); + + fprintf(f, + "<http://example.org/s> <http://example.org/p3> " + "<http://example.org/o3> ."); + + fseek(f, 0, SEEK_SET); + + ReaderTest rt = {0, 0, 0, 0}; + SerdReader* const reader = serd_reader_new( + SERD_NQUADS, &rt, NULL, base_sink, prefix_sink, statement_sink, end_sink); + + assert(reader); + assert(serd_reader_get_handle(reader) == &rt); + assert(f); + + SerdStatus st = serd_reader_start_source_stream( + reader, (SerdSource)fread, (SerdStreamErrorFunc)ferror, f, NULL, 32U); + + assert(st == SERD_SUCCESS); + + // Read first statement + st = serd_reader_read_chunk(reader); + assert(st == SERD_SUCCESS); + assert(rt.n_base == 0); + assert(rt.n_prefix == 0); + assert(rt.n_statement == 1); + assert(rt.n_end == 0); + + // Read second statement + st = serd_reader_read_chunk(reader); + assert(st == SERD_SUCCESS); + assert(rt.n_base == 0); + assert(rt.n_prefix == 0); + assert(rt.n_statement == 2); + assert(rt.n_end == 0); + + // Read terminator + st = serd_reader_read_chunk(reader); + assert(st == SERD_FAILURE); + assert(rt.n_base == 0); + assert(rt.n_prefix == 0); + assert(rt.n_statement == 2); + assert(rt.n_end == 0); + + // Read last statement + st = serd_reader_read_chunk(reader); + assert(st == SERD_SUCCESS); + assert(rt.n_base == 0); + assert(rt.n_prefix == 0); + assert(rt.n_statement == 3); + assert(rt.n_end == 0); + + // EOF + st = serd_reader_read_chunk(reader); + assert(st == SERD_FAILURE); + assert(rt.n_base == 0); + assert(rt.n_prefix == 0); + assert(rt.n_statement == 3); + assert(rt.n_end == 0); + + assert(serd_reader_read_chunk(reader) == SERD_FAILURE); + serd_reader_end_stream(reader); + serd_reader_free(reader); + fclose(f); + remove(path); +} + +static void +test_read_turtle_chunks(const char* const path) +{ + static const char null = 0; + + FILE* const f = fopen(path, "w+b"); + + // Write two statements separated by null characters + fprintf(f, "@base <http://example.org/base/> .\n"); + fprintf(f, "@prefix eg: <http://example.org/> .\n"); + fprintf(f, "eg:s eg:p1 eg:o1 ;\n"); + fprintf(f, " eg:p2 eg:o2 .\n"); + fwrite(&null, sizeof(null), 1, f); + fprintf(f, "eg:s eg:p [ eg:sp eg:so ] ."); + fseek(f, 0, SEEK_SET); + + ReaderTest rt = {0, 0, 0, 0}; + SerdReader* const reader = serd_reader_new( + SERD_TURTLE, &rt, NULL, base_sink, prefix_sink, statement_sink, end_sink); + + assert(reader); + assert(serd_reader_get_handle(reader) == &rt); + assert(f); + + SerdStatus st = serd_reader_start_source_stream( + reader, (SerdSource)fread, (SerdStreamErrorFunc)ferror, f, NULL, 32U); + assert(st == SERD_SUCCESS); + + // Read base + st = serd_reader_read_chunk(reader); + assert(st == SERD_SUCCESS); + assert(rt.n_base == 1); + assert(rt.n_prefix == 0); + assert(rt.n_statement == 0); + assert(rt.n_end == 0); + + // Read prefix + st = serd_reader_read_chunk(reader); + assert(st == SERD_SUCCESS); + assert(rt.n_base == 1); + assert(rt.n_prefix == 1); + assert(rt.n_statement == 0); + assert(rt.n_end == 0); + + // Read first two statements + st = serd_reader_read_chunk(reader); + assert(st == SERD_SUCCESS); + assert(rt.n_base == 1); + assert(rt.n_prefix == 1); + assert(rt.n_statement == 2); + assert(rt.n_end == 0); + + // Read terminator + st = serd_reader_read_chunk(reader); + assert(st == SERD_FAILURE); + assert(rt.n_base == 1); + assert(rt.n_prefix == 1); + assert(rt.n_statement == 2); + assert(rt.n_end == 0); + + // Read statements after null terminator + st = serd_reader_read_chunk(reader); + assert(st == SERD_SUCCESS); + assert(rt.n_base == 1); + assert(rt.n_prefix == 1); + assert(rt.n_statement == 4); + assert(rt.n_end == 1); + + // Read terminator + st = serd_reader_read_chunk(reader); + assert(st == SERD_FAILURE); + assert(rt.n_base == 1); + assert(rt.n_prefix == 1); + assert(rt.n_statement == 4); + assert(rt.n_end == 1); + + // EOF + st = serd_reader_read_chunk(reader); + assert(st == SERD_FAILURE); + assert(rt.n_base == 1); + assert(rt.n_prefix == 1); + assert(rt.n_statement == 4); + assert(rt.n_end == 1); + + assert(serd_reader_read_chunk(reader) == SERD_FAILURE); + serd_reader_end_stream(reader); + serd_reader_free(reader); + fclose(f); + remove(path); +} + +int +main(void) +{ +#ifdef _WIN32 + char tmp[MAX_PATH] = {0}; + const size_t tmp_len = (size_t)GetTempPath(sizeof(tmp), tmp); +#else + const char* const env_tmp = getenv("TMPDIR"); + const char* const tmp = env_tmp ? env_tmp : "/tmp"; + const size_t tmp_len = strlen(tmp); +#endif + + const char* const ttl_name = "serd_test_reader.ttl"; + const char* const nq_name = "serd_test_reader.nq"; + const size_t ttl_name_len = strlen(ttl_name); + const size_t nq_name_len = strlen(nq_name); + const size_t path_len = tmp_len + 1 + ttl_name_len; + char* const path = (char*)calloc(path_len + 1, 1); + + memcpy(path, tmp, tmp_len + 1); + path[tmp_len] = '/'; + + memcpy(path + tmp_len + 1, nq_name, nq_name_len + 1); + test_read_nquads_chunks(path); + + memcpy(path + tmp_len + 1, ttl_name, ttl_name_len + 1); + test_read_turtle_chunks(path); + + test_read_string(); + test_read_eof_file(path); + test_read_eof_by_byte(); + assert(!remove(path)); + + free(path); + return 0; +} diff --git a/test/test_reader_writer.c b/test/test_reader_writer.c index cd7ca408..515d5e5b 100644 --- a/test/test_reader_writer.c +++ b/test/test_reader_writer.c @@ -1,4 +1,4 @@ -// Copyright 2011-2023 David Robillard <d@drobilla.net> +// Copyright 2011-2024 David Robillard <d@drobilla.net> // SPDX-License-Identifier: ISC #undef NDEBUG @@ -11,7 +11,6 @@ #include <assert.h> #include <errno.h> -#include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -25,10 +24,7 @@ typedef struct { } ErrorContext; typedef struct { - int n_base; - int n_prefix; int n_statement; - int n_end; const SerdNode* graph; } ReaderTest; @@ -41,6 +37,8 @@ static const char* const doc_string = " \"lang\"@en ;\n" " eg:p <http://example.com/o> .\n" "}\n" + "@prefix other: <http://example.org/other> .\n" + "@base <http://drobilla.net/> .\n" "eg:s\n" " <http://example.org/p> [\n" " eg:p 3.0 ,\n" @@ -56,35 +54,14 @@ static const char* const doc_string = "( eg:o ) eg:t eg:u .\n"; static SerdStatus -test_base_sink(void* handle, const SerdNode* uri) -{ - (void)uri; - - ReaderTest* rt = (ReaderTest*)handle; - ++rt->n_base; - return SERD_SUCCESS; -} - -static SerdStatus -test_prefix_sink(void* handle, const SerdNode* name, const SerdNode* uri) -{ - (void)name; - (void)uri; - - ReaderTest* rt = (ReaderTest*)handle; - ++rt->n_prefix; - return SERD_SUCCESS; -} - -static SerdStatus -test_statement_sink(void* handle, - SerdStatementFlags flags, - const SerdNode* graph, - const SerdNode* subject, - const SerdNode* predicate, - const SerdNode* object, - const SerdNode* object_datatype, - const SerdNode* object_lang) +test_statement_sink(void* const handle, + const SerdStatementFlags flags, + const SerdNode* const graph, + const SerdNode* const subject, + const SerdNode* const predicate, + const SerdNode* const object, + const SerdNode* const object_datatype, + const SerdNode* const object_lang) { (void)flags; (void)subject; @@ -99,274 +76,6 @@ test_statement_sink(void* handle, return SERD_SUCCESS; } -static SerdStatus -test_end_sink(void* handle, const SerdNode* node) -{ - (void)node; - - ReaderTest* rt = (ReaderTest*)handle; - ++rt->n_end; - return SERD_SUCCESS; -} - -/// Reads a null byte after a statement, then succeeds again (like a socket) -static size_t -eof_test_read(void* buf, size_t size, size_t nmemb, void* stream) -{ - assert(size == 1); - assert(nmemb == 1); - (void)size; - - static const char* const string = "_:s1 <http://example.org/p> _:o1 .\n" - "_:s2 <http://example.org/p> _:o2 .\n"; - - size_t* const count = (size_t*)stream; - - // Normal reading for the first statement - if (*count < 35) { - *(char*)buf = string[*count]; - ++*count; - return nmemb; - } - - // EOF for the first read at the start of the second statement - if (*count == 35) { - assert(string[*count] == '_'); - ++*count; - return 0; - } - - if (*count >= strlen(string)) { - return 0; - } - - // Normal reading after the EOF, adjusting for the skipped index 35 - *(char*)buf = string[*count - 1]; - ++*count; - return nmemb; -} - -static int -eof_test_error(void* stream) -{ - (void)stream; - return 0; -} - -static void -test_read_nquads_chunks(const char* const path) -{ - static const char null = 0; - - FILE* const f = fopen(path, "w+b"); - - // Write two statements, a null separator, then another statement - - fprintf(f, - "<http://example.org/s> <http://example.org/p1> " - "<http://example.org/o1> .\n"); - - fprintf(f, - "<http://example.org/s> <http://example.org/p2> " - "<http://example.org/o2> .\n"); - - fwrite(&null, sizeof(null), 1, f); - - fprintf(f, - "<http://example.org/s> <http://example.org/p3> " - "<http://example.org/o3> .\n"); - - fseek(f, 0, SEEK_SET); - - ReaderTest* const rt = (ReaderTest*)calloc(1, sizeof(ReaderTest)); - SerdReader* const reader = serd_reader_new(SERD_NQUADS, - rt, - free, - test_base_sink, - test_prefix_sink, - test_statement_sink, - test_end_sink); - - assert(reader); - assert(serd_reader_get_handle(reader) == rt); - assert(f); - - SerdStatus st = serd_reader_start_stream(reader, f, NULL, false); - assert(st == SERD_SUCCESS); - - // Read first statement - st = serd_reader_read_chunk(reader); - assert(st == SERD_SUCCESS); - assert(rt->n_base == 0); - assert(rt->n_prefix == 0); - assert(rt->n_statement == 1); - assert(rt->n_end == 0); - - // Read second statement - st = serd_reader_read_chunk(reader); - assert(st == SERD_SUCCESS); - assert(rt->n_base == 0); - assert(rt->n_prefix == 0); - assert(rt->n_statement == 2); - assert(rt->n_end == 0); - - // Read terminator - st = serd_reader_read_chunk(reader); - assert(st == SERD_FAILURE); - assert(rt->n_base == 0); - assert(rt->n_prefix == 0); - assert(rt->n_statement == 2); - assert(rt->n_end == 0); - - // Read last statement - st = serd_reader_read_chunk(reader); - assert(st == SERD_SUCCESS); - assert(rt->n_base == 0); - assert(rt->n_prefix == 0); - assert(rt->n_statement == 3); - assert(rt->n_end == 0); - - // EOF - st = serd_reader_read_chunk(reader); - assert(st == SERD_FAILURE); - assert(rt->n_base == 0); - assert(rt->n_prefix == 0); - assert(rt->n_statement == 3); - assert(rt->n_end == 0); - - assert(serd_reader_read_chunk(reader) == SERD_FAILURE); - - serd_reader_free(reader); - fclose(f); - remove(path); -} - -static void -test_read_turtle_chunks(const char* const path) -{ - static const char null = 0; - - FILE* const f = fopen(path, "w+b"); - - // Write two statements separated by null characters - fprintf(f, "@base <http://example.org/base/> .\n"); - fprintf(f, "@prefix eg: <http://example.org/> .\n"); - fprintf(f, "eg:s eg:p1 eg:o1 ;\n"); - fprintf(f, " eg:p2 eg:o2 .\n"); - fwrite(&null, sizeof(null), 1, f); - fprintf(f, "eg:s eg:p [ eg:sp eg:so ] .\n"); - fwrite(&null, sizeof(null), 1, f); - fseek(f, 0, SEEK_SET); - - ReaderTest* const rt = (ReaderTest*)calloc(1, sizeof(ReaderTest)); - SerdReader* const reader = serd_reader_new(SERD_TURTLE, - rt, - free, - test_base_sink, - test_prefix_sink, - test_statement_sink, - test_end_sink); - - assert(reader); - assert(serd_reader_get_handle(reader) == rt); - assert(f); - - SerdStatus st = serd_reader_start_stream(reader, f, NULL, false); - assert(st == SERD_SUCCESS); - - // Read base - st = serd_reader_read_chunk(reader); - assert(st == SERD_SUCCESS); - assert(rt->n_base == 1); - assert(rt->n_prefix == 0); - assert(rt->n_statement == 0); - assert(rt->n_end == 0); - - // Read prefix - st = serd_reader_read_chunk(reader); - assert(st == SERD_SUCCESS); - assert(rt->n_base == 1); - assert(rt->n_prefix == 1); - assert(rt->n_statement == 0); - assert(rt->n_end == 0); - - // Read first two statements - st = serd_reader_read_chunk(reader); - assert(st == SERD_SUCCESS); - assert(rt->n_base == 1); - assert(rt->n_prefix == 1); - assert(rt->n_statement == 2); - assert(rt->n_end == 0); - - // Read terminator - st = serd_reader_read_chunk(reader); - assert(st == SERD_FAILURE); - assert(rt->n_base == 1); - assert(rt->n_prefix == 1); - assert(rt->n_statement == 2); - assert(rt->n_end == 0); - - // Read statements after null terminator - st = serd_reader_read_chunk(reader); - assert(st == SERD_SUCCESS); - assert(rt->n_base == 1); - assert(rt->n_prefix == 1); - assert(rt->n_statement == 4); - assert(rt->n_end == 1); - - // Read terminator - st = serd_reader_read_chunk(reader); - assert(st == SERD_FAILURE); - assert(rt->n_base == 1); - assert(rt->n_prefix == 1); - assert(rt->n_statement == 4); - assert(rt->n_end == 1); - - // EOF - st = serd_reader_read_chunk(reader); - assert(st == SERD_FAILURE); - assert(rt->n_base == 1); - assert(rt->n_prefix == 1); - assert(rt->n_statement == 4); - assert(rt->n_end == 1); - - assert(serd_reader_read_chunk(reader) == SERD_FAILURE); - - serd_reader_free(reader); - fclose(f); - remove(path); -} - -static void -test_read_string(void) -{ - ReaderTest* rt = (ReaderTest*)calloc(1, sizeof(ReaderTest)); - SerdReader* reader = serd_reader_new(SERD_TURTLE, - rt, - free, - test_base_sink, - test_prefix_sink, - test_statement_sink, - test_end_sink); - - assert(reader); - assert(serd_reader_get_handle(reader) == rt); - - // Test reading a string that ends exactly at the end of input (no newline) - const SerdStatus st = serd_reader_read_string( - reader, - USTR("<http://example.org/s> <http://example.org/p> " - "<http://example.org/o> .")); - - assert(!st); - assert(rt->n_base == 0); - assert(rt->n_prefix == 0); - assert(rt->n_statement == 1); - assert(rt->n_end == 0); - - serd_reader_free(reader); -} - static size_t faulty_sink(const void* const buf, const size_t len, void* const stream) { @@ -399,7 +108,7 @@ test_write_errors(void) ErrorContext ctx = {0U, 0U}; const SerdStyle style = (SerdStyle)(SERD_STYLE_STRICT | SERD_STYLE_CURIED); - const size_t max_offsets[] = {0, 386, 1911, 2003, 386}; + const size_t max_offsets[] = {0, 462, 1911, 2003, 462}; // Test errors at different offsets to hit different code paths for (unsigned s = 1; s <= (unsigned)SERD_TRIG; ++s) { @@ -455,8 +164,9 @@ test_writer(const char* const path) assert(serd_writer_end_anon(writer, NULL)); assert(serd_writer_get_env(writer) == env); - uint8_t buf[] = {0x80, 0, 0, 0, 0}; - SerdNode s = serd_node_from_string(SERD_URI, USTR("")); + const uint8_t buf[] = {0x80, 0, 0, 0, 0}; + + SerdNode s = serd_node_from_string(SERD_URI, USTR("")); SerdNode p = serd_node_from_string(SERD_URI, USTR("http://example.org/pred")); SerdNode o = serd_node_from_string(SERD_LITERAL, buf); @@ -555,16 +265,11 @@ test_writer(const char* const path) } static void -test_reader(const char* path) +test_reader(const char* const path) { ReaderTest* rt = (ReaderTest*)calloc(1, sizeof(ReaderTest)); - SerdReader* reader = serd_reader_new(SERD_TURTLE, - rt, - free, - test_base_sink, - test_prefix_sink, - test_statement_sink, - test_end_sink); + SerdReader* reader = serd_reader_new( + SERD_TURTLE, rt, free, NULL, NULL, test_statement_sink, NULL); assert(reader); assert(serd_reader_get_handle(reader) == rt); @@ -590,44 +295,12 @@ test_reader(const char* path) const SerdStatus st = serd_reader_read_file(reader, USTR(path)); assert(!st); - assert(rt->n_base == 0); - assert(rt->n_prefix == 0); assert(rt->n_statement == 13); - assert(rt->n_end == 0); assert(rt->graph && rt->graph->buf && !strcmp((const char*)rt->graph->buf, "http://example.org/")); assert(serd_reader_read_string(reader, USTR("This isn't Turtle at all."))); - // A read of a big page hits EOF then fails to read chunks immediately - { - FILE* const in = fopen(path, "rb"); - serd_reader_start_stream(reader, in, (const uint8_t*)"test", true); - - assert(serd_reader_read_chunk(reader) == SERD_SUCCESS); - assert(serd_reader_read_chunk(reader) == SERD_FAILURE); - assert(serd_reader_read_chunk(reader) == SERD_FAILURE); - - serd_reader_end_stream(reader); - fclose(in); - } - - // A byte-wise reader that hits EOF once then continues (like a socket) - { - size_t n_reads = 0; - serd_reader_start_source_stream(reader, - (SerdSource)eof_test_read, - (SerdStreamErrorFunc)eof_test_error, - &n_reads, - NULL, - 1); - - assert(serd_reader_read_chunk(reader) == SERD_SUCCESS); - assert(serd_reader_read_chunk(reader) == SERD_FAILURE); - assert(serd_reader_read_chunk(reader) == SERD_SUCCESS); - assert(serd_reader_read_chunk(reader) == SERD_FAILURE); - } - serd_reader_free(reader); } @@ -644,22 +317,14 @@ main(void) #endif const char* const ttl_name = "serd_test_reader_writer.ttl"; - const char* const nq_name = "serd_test_reader_writer.nq"; const size_t ttl_name_len = strlen(ttl_name); - const size_t nq_name_len = strlen(nq_name); const size_t path_len = tmp_len + 1 + ttl_name_len; char* const path = (char*)calloc(path_len + 1, 1); memcpy(path, tmp, tmp_len + 1); path[tmp_len] = '/'; - - memcpy(path + tmp_len + 1, nq_name, nq_name_len + 1); - test_read_nquads_chunks(path); - memcpy(path + tmp_len + 1, ttl_name, ttl_name_len + 1); - test_read_turtle_chunks(path); - test_read_string(); test_write_errors(); test_writer(path); @@ -668,6 +333,5 @@ main(void) assert(!remove(path)); free(path); - printf("Success\n"); return 0; } diff --git a/test/test_stdin.py b/test/test_stdin.py index f976ca52..fb01f4ee 100755 --- a/test/test_stdin.py +++ b/test/test_stdin.py @@ -7,37 +7,14 @@ # pylint: disable=consider-using-f-string -import argparse -import sys -import shlex -import subprocess -import tempfile +import serd_test_util as util -parser = argparse.ArgumentParser(description=__doc__) +args = util.wrapper_args(__doc__) +command = [args.serdi, "-i", "ntriples", "-", "http://example.org"] -parser.add_argument("--serdi", default="./serdi", help="path to serdi") -parser.add_argument("--wrapper", default="", help="executable wrapper") +DOC = "<{0}s> <{0}p> <{0}o> .".format("http://example.org/") -args = parser.parse_args(sys.argv[1:]) -command = shlex.split(args.wrapper) + [args.serdi, "-"] +lines = util.command_output(args.wrapper, command, DOC).splitlines(True) -DOCUMENT = "<{0}s> <{0}p> <{0}o> .".format("http://example.org/") - -with tempfile.TemporaryFile() as out: - proc = subprocess.run( - command, - check=False, - encoding="utf-8", - input=DOCUMENT, - stdout=out, - stderr=subprocess.PIPE, - ) - - assert proc.returncode == 0 - assert args.wrapper or len(proc.stderr) == 0 - - out.seek(0) - lines = out.readlines() - - assert len(lines) == 1 - assert lines[0].decode("utf-8").strip() == DOCUMENT +assert len(lines) == 1 +assert lines[0].strip() == DOC diff --git a/test/test_string.c b/test/test_string.c index 6767e5ae..1af3f45a 100644 --- a/test/test_string.c +++ b/test/test_string.c @@ -7,23 +7,34 @@ #include <assert.h> #include <stdint.h> -#include <stdio.h> #include <string.h> static void +check_strlen(const char* const str, + const size_t expected_n_bytes, + const size_t expected_n_chars, + const SerdNodeFlags expected_flags) +{ + size_t n_bytes = 0U; + SerdNodeFlags flags = 0U; + const size_t n_chars = serd_strlen((const uint8_t*)str, &n_bytes, &flags); + + assert(n_bytes == expected_n_bytes); + assert(n_chars == expected_n_chars); + assert(flags == expected_flags); +} + +static void test_strlen(void) { - const uint8_t str[] = {'"', '5', 0xE2, 0x82, 0xAC, '"', '\n', 0}; + static const uint8_t utf8[] = {'"', '5', 0xE2, 0x82, 0xAC, '"', '\n', 0}; - size_t n_bytes = 0; - SerdNodeFlags flags = 0; - size_t len = serd_strlen(str, &n_bytes, &flags); - assert(len == 5 && n_bytes == 7 && - flags == (SERD_HAS_QUOTE | SERD_HAS_NEWLINE)); - len = serd_strlen(str, NULL, &flags); - assert(len == 5); + check_strlen("\"quotes\"", 8U, 8U, SERD_HAS_QUOTE); + check_strlen("newline\n", 8U, 8U, SERD_HAS_NEWLINE); + check_strlen("\rreturn", 7U, 7U, SERD_HAS_NEWLINE); + check_strlen((const char*)utf8, 7U, 5U, SERD_HAS_QUOTE | SERD_HAS_NEWLINE); - assert(serd_strlen(str, &n_bytes, NULL) == 5); + assert(serd_strlen((const uint8_t*)"nulls", NULL, NULL) == 5U); } static void @@ -45,7 +56,5 @@ main(void) { test_strlen(); test_strerror(); - - printf("Success\n"); return 0; } diff --git a/test/test_uri.c b/test/test_uri.c index cc81b40e..7315a0fc 100644 --- a/test/test_uri.c +++ b/test/test_uri.c @@ -8,7 +8,6 @@ #include <assert.h> #include <stdbool.h> #include <stdint.h> -#include <stdio.h> #include <string.h> #define USTR(s) ((const uint8_t*)(s)) @@ -16,6 +15,8 @@ static void test_uri_string_has_scheme(void) { + assert(!serd_uri_string_has_scheme(NULL)); + assert(!serd_uri_string_has_scheme(USTR("relative"))); assert(!serd_uri_string_has_scheme(USTR("http"))); assert(!serd_uri_string_has_scheme(USTR("5nostartdigit"))); @@ -35,11 +36,11 @@ test_uri_string_has_scheme(void) } static void -test_file_uri(const char* const hostname, - const char* const path, - const bool escape, - const char* const expected_uri, - const char* expected_path) +check_file_uri(const char* const hostname, + const char* const path, + const bool escape, + const char* const expected_uri, + const char* expected_path) { if (!expected_path) { expected_path = path; @@ -99,6 +100,8 @@ test_uri_to_path(void) "C|/Windows/Sucks")); assert(!serd_uri_to_path((const uint8_t*)"http://example.org/path")); + + assert(!strcmp((const char*)serd_uri_to_path((const uint8_t*)"rel"), "rel")); } #if defined(__GNUC__) @@ -108,69 +111,78 @@ test_uri_to_path(void) static void test_uri_parsing(void) { - test_file_uri(NULL, "C:/My 100%", true, "file:///C:/My%20100%%", NULL); - test_file_uri(NULL, "/foo/bar", true, "file:///foo/bar", NULL); - test_file_uri("bhost", "/foo/bar", true, "file://bhost/foo/bar", NULL); - test_file_uri(NULL, "a/relative path", false, "a/relative path", NULL); - test_file_uri( + check_file_uri(NULL, "C:/My 100%", true, "file:///C:/My%20100%%", NULL); + check_file_uri(NULL, "/foo/bar", true, "file:///foo/bar", NULL); + check_file_uri("bhost", "/foo/bar", true, "file://bhost/foo/bar", NULL); + check_file_uri(NULL, "a/relative path", false, "a/relative path", NULL); + check_file_uri( NULL, "a/relative <path>", true, "a/relative%20%3Cpath%3E", NULL); #ifdef _WIN32 - test_file_uri( + check_file_uri( NULL, "C:\\My 100%", true, "file:///C:/My%20100%%", "C:/My 100%"); - test_file_uri(NULL, - "\\drive\\relative", - true, - "file:///drive/relative", - "/drive/relative"); - - test_file_uri(NULL, - "C:\\Program Files\\Serd", - true, - "file:///C:/Program%20Files/Serd", - "C:/Program Files/Serd"); - - test_file_uri("ahost", - "C:\\Pointless Space", - true, - "file://ahost/C:/Pointless%20Space", - "C:/Pointless Space"); + check_file_uri(NULL, + "\\drive\\relative", + true, + "file:///drive/relative", + "/drive/relative"); + + check_file_uri(NULL, + "C:\\Program Files\\Serd", + true, + "file:///C:/Program%20Files/Serd", + "C:/Program Files/Serd"); + + check_file_uri("ahost", + "C:\\Pointless Space", + true, + "file://ahost/C:/Pointless%20Space", + "C:/Pointless Space"); #else /* What happens with Windows paths on other platforms is a bit weird, but more or less unavoidable. It doesn't work to interpret backslashes as path separators on any other platform. */ - test_file_uri("ahost", - "C:\\Pointless Space", - true, - "file://ahost/C:%5CPointless%20Space", - "/C:\\Pointless Space"); - - test_file_uri(NULL, - "\\drive\\relative", - true, - "%5Cdrive%5Crelative", - "\\drive\\relative"); - - test_file_uri(NULL, - "C:\\Program Files\\Serd", - true, - "file:///C:%5CProgram%20Files%5CSerd", - "/C:\\Program Files\\Serd"); - - test_file_uri("ahost", - "C:\\Pointless Space", - true, - "file://ahost/C:%5CPointless%20Space", - "/C:\\Pointless Space"); + check_file_uri("ahost", + "C:\\Pointless Space", + true, + "file://ahost/C:%5CPointless%20Space", + "/C:\\Pointless Space"); + + check_file_uri(NULL, + "\\drive\\relative", + true, + "%5Cdrive%5Crelative", + "\\drive\\relative"); + + check_file_uri(NULL, + "C:\\Program Files\\Serd", + true, + "file:///C:%5CProgram%20Files%5CSerd", + "/C:\\Program Files\\Serd"); + + check_file_uri("ahost", + "C:\\Pointless Space", + true, + "file://ahost/C:%5CPointless%20Space", + "/C:\\Pointless Space"); #endif + // Test tolerance of NULL hostname parameter + uint8_t* const hosted = serd_file_uri_parse(USTR("file://host/path"), NULL); + assert(!strcmp((const char*)hosted, "/path")); + serd_free(hosted); + // Test tolerance of parsing junk URI escapes - uint8_t* out_path = serd_file_uri_parse(USTR("file:///foo/%0Xbar"), NULL); - assert(!strcmp((const char*)out_path, "/foo/bar")); - serd_free(out_path); + uint8_t* const junk1 = serd_file_uri_parse(USTR("file:///foo/%0Xbar"), NULL); + assert(!strcmp((const char*)junk1, "/foo/bar")); + serd_free(junk1); + + uint8_t* const junk2 = serd_file_uri_parse(USTR("file:///foo/%X0bar"), NULL); + assert(!strcmp((const char*)junk2, "/foo/bar")); + serd_free(junk2); } static void @@ -195,7 +207,7 @@ test_uri_from_string(void) } static inline bool -chunk_equals(const SerdChunk* a, const SerdChunk* b) +chunk_equals(const SerdChunk* const a, const SerdChunk* const b) { return (!a->len && !b->len && !a->buf && !b->buf) || (a->len && b->len && a->buf && b->buf && @@ -343,6 +355,22 @@ test_relative_uri(void) "http://example.org/a/b/c", "http://example.org/a/b", "http://example.org/a"); + + // Tolerance of NULL URI output parameter + { + SerdURI uri = SERD_URI_NULL; + assert(!serd_uri_parse(USTR("http://example.org/path"), &uri)); + + SerdURI base = SERD_URI_NULL; + assert(!serd_uri_parse(USTR("http://example.org/"), &base)); + + SerdNode result_node = serd_node_new_relative_uri(&uri, &base, NULL, NULL); + + assert(result_node.n_bytes == 4U); + assert(!strcmp((const char*)result_node.buf, "path")); + + serd_node_free(&result_node); + } } int @@ -353,7 +381,5 @@ main(void) test_uri_parsing(); test_uri_from_string(); test_relative_uri(); - - printf("Success\n"); return 0; } diff --git a/test/test_write_error.py b/test/test_write_error.py index 35fde232..b62f981a 100755 --- a/test/test_write_error.py +++ b/test/test_write_error.py @@ -5,19 +5,14 @@ """Test errors writing to a file.""" -import argparse import sys import shlex import subprocess import os -parser = argparse.ArgumentParser(description=__doc__) +import serd_test_util as util -parser.add_argument("--serdi", default="./serdi", help="path to serdi") -parser.add_argument("--wrapper", default="", help="executable wrapper") -parser.add_argument("input", help="valid input file") - -args = parser.parse_args(sys.argv[1:]) +args = util.wrapper_args(__doc__, True) command = shlex.split(args.wrapper) + [args.serdi, args.input] if os.path.exists("/dev/full"): diff --git a/test/test_writer.c b/test/test_writer.c index b02aba7e..9e94e139 100644 --- a/test/test_writer.c +++ b/test/test_writer.c @@ -10,6 +10,8 @@ #include <stdio.h> #include <string.h> +#define NS_EG "http://example.org/" + #define USTR(s) ((const uint8_t*)(s)) static void @@ -22,8 +24,8 @@ test_write_long_literal(void) assert(writer); - SerdNode s = serd_node_from_string(SERD_URI, USTR("http://example.org/s")); - SerdNode p = serd_node_from_string(SERD_URI, USTR("http://example.org/p")); + SerdNode s = serd_node_from_string(SERD_URI, USTR(NS_EG "s")); + SerdNode p = serd_node_from_string(SERD_URI, USTR(NS_EG "p")); SerdNode o = serd_node_from_string(SERD_LITERAL, USTR("hello \"\"\"world\"\"\"!")); @@ -52,16 +54,16 @@ test_write_nested_anon(void) assert(writer); - SerdNode s0 = serd_node_from_string(SERD_URI, USTR("http://example.org/s0")); - SerdNode p0 = serd_node_from_string(SERD_URI, USTR("http://example.org/p0")); + SerdNode s0 = serd_node_from_string(SERD_URI, USTR(NS_EG "s0")); + SerdNode p0 = serd_node_from_string(SERD_URI, USTR(NS_EG "p0")); SerdNode b0 = serd_node_from_string(SERD_BLANK, USTR("b0")); - SerdNode p1 = serd_node_from_string(SERD_URI, USTR("http://example.org/p1")); + SerdNode p1 = serd_node_from_string(SERD_URI, USTR(NS_EG "p1")); SerdNode b1 = serd_node_from_string(SERD_BLANK, USTR("b1")); - SerdNode p2 = serd_node_from_string(SERD_URI, USTR("http://example.org/p2")); - SerdNode o2 = serd_node_from_string(SERD_URI, USTR("http://example.org/o2")); - SerdNode p3 = serd_node_from_string(SERD_URI, USTR("http://example.org/p3")); - SerdNode p4 = serd_node_from_string(SERD_URI, USTR("http://example.org/p4")); - SerdNode o4 = serd_node_from_string(SERD_URI, USTR("http://example.org/o4")); + SerdNode p2 = serd_node_from_string(SERD_URI, USTR(NS_EG "p2")); + SerdNode o2 = serd_node_from_string(SERD_URI, USTR(NS_EG "o2")); + SerdNode p3 = serd_node_from_string(SERD_URI, USTR(NS_EG "p3")); + SerdNode p4 = serd_node_from_string(SERD_URI, USTR(NS_EG "p4")); + SerdNode o4 = serd_node_from_string(SERD_URI, USTR(NS_EG "o4")); SerdNode nil = serd_node_from_string( SERD_URI, USTR("http://www.w3.org/1999/02/22-rdf-syntax-ns#nil")); @@ -110,7 +112,6 @@ test_write_nested_anon(void) "\t\t<http://example.org/p4> <http://example.org/o4>\n" "\t] .\n"; - fprintf(stderr, "%s\n", out); assert(!strcmp((char*)out, expected)); serd_free(out); } @@ -132,9 +133,11 @@ test_writer_cleanup(void) SerdWriter* writer = serd_writer_new(SERD_TURTLE, (SerdStyle)0U, env, NULL, null_sink, NULL); - SerdNode s = serd_node_from_string(SERD_URI, USTR("http://example.org/s")); - SerdNode p = serd_node_from_string(SERD_URI, USTR("http://example.org/p")); - SerdNode o = serd_node_from_string(SERD_BLANK, USTR("http://example.org/o")); + SerdNode s = serd_node_from_string(SERD_URI, USTR(NS_EG "s")); + SerdNode p = serd_node_from_string(SERD_URI, USTR(NS_EG "p")); + + char o_buf[12] = {'b', '0', '\0'}; + SerdNode o = serd_node_from_string(SERD_BLANK, USTR(o_buf)); st = serd_writer_write_statement( writer, SERD_ANON_O_BEGIN, NULL, &s, &p, &o, NULL, NULL); @@ -142,16 +145,24 @@ test_writer_cleanup(void) assert(!st); // Write the start of several nested anonymous objects - for (unsigned i = 0U; !st && i < 8U; ++i) { - char buf[12] = {0}; - snprintf(buf, sizeof(buf), "b%u", i); + for (unsigned i = 1U; !st && i < 9U; ++i) { + char next_o_buf[12] = {'\0'}; + snprintf(next_o_buf, sizeof(next_o_buf), "b%u", i); + + SerdNode next_o = serd_node_from_string(SERD_BLANK, USTR(next_o_buf)); - SerdNode next_o = serd_node_from_string(SERD_BLANK, USTR(buf)); + st = serd_writer_write_statement(writer, + SERD_ANON_O_BEGIN | SERD_ANON_CONT, + NULL, + &o, + &p, + &next_o, + NULL, + NULL); - st = serd_writer_write_statement( - writer, SERD_ANON_O_BEGIN, NULL, &o, &p, &next_o, NULL, NULL); + assert(!st); - o = next_o; + memcpy(o_buf, next_o_buf, sizeof(o_buf)); } // Finish writing without terminating nodes @@ -166,6 +177,35 @@ test_writer_cleanup(void) } static void +test_write_bad_anon_stack(void) +{ + SerdStatus st = SERD_SUCCESS; + SerdEnv* env = serd_env_new(NULL); + SerdWriter* writer = + serd_writer_new(SERD_TURTLE, (SerdStyle)0U, env, NULL, null_sink, NULL); + + SerdNode s = serd_node_from_string(SERD_URI, USTR(NS_EG "s")); + SerdNode p = serd_node_from_string(SERD_URI, USTR(NS_EG "p")); + SerdNode b0 = serd_node_from_string(SERD_BLANK, USTR("b0")); + SerdNode b1 = serd_node_from_string(SERD_BLANK, USTR("b1")); + SerdNode b2 = serd_node_from_string(SERD_BLANK, USTR("b2")); + + assert(!(st = serd_writer_write_statement( + writer, SERD_ANON_O_BEGIN, NULL, &s, &p, &b0, NULL, NULL))); + + // (missing call to end the anonymous node here) + + st = serd_writer_write_statement( + writer, SERD_ANON_O_BEGIN, NULL, &b1, &p, &b2, NULL, NULL); + + assert(st == SERD_ERR_BAD_ARG); + + assert(!(st = serd_writer_finish(writer))); + serd_writer_free(writer); + serd_env_free(env); +} + +static void test_strict_write(void) { const char* const path = "serd_strict_write_test.ttl"; @@ -180,8 +220,8 @@ test_strict_write(void) const uint8_t bad_str[] = {0xFF, 0x90, 'h', 'i', 0}; - SerdNode s = serd_node_from_string(SERD_URI, USTR("http://example.org/s")); - SerdNode p = serd_node_from_string(SERD_URI, USTR("http://example.org/p")); + SerdNode s = serd_node_from_string(SERD_URI, USTR(NS_EG "s")); + SerdNode p = serd_node_from_string(SERD_URI, USTR(NS_EG "p")); SerdNode bad_lit = serd_node_from_string(SERD_LITERAL, bad_str); SerdNode bad_uri = serd_node_from_string(SERD_URI, bad_str); @@ -233,8 +273,11 @@ main(void) test_write_long_literal(); test_write_nested_anon(); test_writer_cleanup(); + test_write_bad_anon_stack(); test_strict_write(); test_write_error(); return 0; } + +#undef NS_EG |