diff options
| author | kx <kx@radix-linux.su> | 2026-10-01 12:02:28 +0300 |
|---|---|---|
| committer | kx <kx@radix-linux.su> | 2026-10-01 12:02:28 +0300 |
| commit | ba1b04d64bdfaf377915b22f77520216ebe47674 (patch) | |
| tree | 044343035c28b2b04ca9725f26e9b7c39342d19c /tests | |
| parent | 124b140798456778e2a96851cc7268aa9a726698 (diff) | |
| download | mcpu-cpp-1.0.2.tar.xz | |
Version 1.0.21.0.2
Diffstat (limited to 'tests')
84 files changed, 4493 insertions, 0 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..ade9bae --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,101 @@ + +TESTS = \ + t0001-utf8.sh \ + t0002-lang.sh \ + t0003-include.sh \ + t0004-config.sh \ + t0005-errors.sh \ + t0006-path-order.sh \ + t0007-language-path.sh \ + t0008-comments.sh \ + t0009-text-scanner.sh \ + t0010-language-names.sh \ + t0011-base-include-path.sh \ + t0012-user-config-path.sh \ + t0013-include-precedence.sh \ + t0014-preprocessing-phases.sh \ + t0015-object-macros.sh \ + t0016-macro-include.sh \ + t0017-macro-errors.sh \ + t0018-include-lexing.sh \ + t0019-recursive-macro.sh \ + t0020-function-macros.sh \ + t0021-function-macro-errors.sh \ + t0022-predefined-macros.sh \ + t0023-predefined-redefine.sh \ + t0024-abi-predefined.sh \ + t0025-dump-macros.sh \ + t0026-dump-config.sh \ + t0027-system-language-path.sh \ + t0028-predefined-ranges.sh \ + t0029-predefined-abi-names.sh \ + t0030-integer-decimal-digits.sh \ + t0031-stringification.sh \ + t0032-command-line.sh \ + t0033-dump-definitions.sh \ + t0034-conditionals.sh \ + t0035-line-control.sh \ + t0036-lang-string.sh \ + t0037-token-concatenation.sh \ + t0038-ucs2-identifiers.sh \ + t0039-command-line-macros.sh \ + t0040-zubr-expression.sh \ + t0041-integer-width-suffix.sh \ + t0042-diagnostics.sh \ + t0043-include-next.sh \ + t0044-configured-search-order.sh \ + t0045-search-dirs-verbose.sh \ + t0046-pragma-once.sh \ + t0047-dependencies.sh \ + t0048-no-config-defaults.sh \ + t0049-relocatable-root.sh \ + t0050-dependency-side-effects.sh \ + t0051-dump-macro-origin.sh \ + t0052-warning-control.sh \ + t0053-interface-cleanup.sh \ + t0054-inhibit-warnings.sh \ + t0055-forced-files.sh \ + t0056-dependency-targets.sh \ + t0057-missing-generated-dependencies.sh \ + t0058-variadic-macros.sh \ + t0059-va-opt.sh \ + t0060-macro-whitespace.sh \ + t0061-output-line-compaction.sh + +noinst_PROGRAMS = mcpp-options-test + +mcpp_options_test_SOURCES = \ + mcpp-options-test.c \ + ../src/mcpp-options.c + +mcpp_options_test_CPPFLAGS = -I$(top_srcdir)/src $(LIBMPUIO_CFLAGS) +mcpp_options_test_LDADD = $(LIBMPUIO_LIBS) + +EXTRA_DIST = $(TESTS) \ + data/utf8.c \ + data/lang.c \ + data/include-main.c \ + data/include/one.h \ + data/include/bridge.h \ + data/system/system.h \ + data/config.conf \ + data/config-main.c \ + data/unbalanced.c \ + data/nonucs2.c \ + data/order-main.c \ + data/user/order.h \ + data/system/order.h \ + data/lang-path-main.c \ + data/lang-as/lang.h \ + data/lang-diff/lang.h \ + data/base-path-main.c \ + data/lang-base/base.h \ + data/lang-base/diff/explicit.h \ + data/common-precedence/same.h \ + data/lang-as-precedence/same.h + +TESTS_ENVIRONMENT = MCPU_CPP='$(abs_top_builddir)/src/mcpu-cpp'; export MCPU_CPP; \ + MCPP_OPTIONS_TEST='$(abs_top_builddir)/tests/mcpp-options-test'; export MCPP_OPTIONS_TEST; + +distclean-local: + -rm -rf $(DEPDIR) diff --git a/tests/data/base-path-main.c b/tests/data/base-path-main.c new file mode 100644 index 0000000..6ca1ffb --- /dev/null +++ b/tests/data/base-path-main.c @@ -0,0 +1 @@ +#include <base.h> diff --git a/tests/data/common-precedence/same.h b/tests/data/common-precedence/same.h new file mode 100644 index 0000000..8a7b2c8 --- /dev/null +++ b/tests/data/common-precedence/same.h @@ -0,0 +1 @@ +int from_common_include_root; diff --git a/tests/data/config-main.c b/tests/data/config-main.c new file mode 100644 index 0000000..892eb5e --- /dev/null +++ b/tests/data/config-main.c @@ -0,0 +1,2 @@ +#include "one.h" +#include <system.h> diff --git a/tests/data/config.conf b/tests/data/config.conf new file mode 100644 index 0000000..e395802 --- /dev/null +++ b/tests/data/config.conf @@ -0,0 +1,2 @@ +MCPU_CPP_INCLUDE_PATH = ./include; +MCPU_CPP_SYSTEM_INCLUDE_PATH = ./system; diff --git a/tests/data/include-main.c b/tests/data/include-main.c new file mode 100644 index 0000000..93bca59 --- /dev/null +++ b/tests/data/include-main.c @@ -0,0 +1,6 @@ +#include "one.h" +#include <system.h> +#lang "diff" +#include "bridge.h" +y' = 1; +#endlang diff --git a/tests/data/include/bridge.h b/tests/data/include/bridge.h new file mode 100644 index 0000000..a878df1 --- /dev/null +++ b/tests/data/include/bridge.h @@ -0,0 +1,4 @@ +/* Entered while current language is diff. */ +#endlang +int bridge_c; +#lang "diff" diff --git a/tests/data/include/one.h b/tests/data/include/one.h new file mode 100644 index 0000000..6200094 --- /dev/null +++ b/tests/data/include/one.h @@ -0,0 +1 @@ +int from_one; diff --git a/tests/data/lang-as-precedence/same.h b/tests/data/lang-as-precedence/same.h new file mode 100644 index 0000000..f139324 --- /dev/null +++ b/tests/data/lang-as-precedence/same.h @@ -0,0 +1 @@ +int from_as_specific_include_path; diff --git a/tests/data/lang-as/lang.h b/tests/data/lang-as/lang.h new file mode 100644 index 0000000..f1f9283 --- /dev/null +++ b/tests/data/lang-as/lang.h @@ -0,0 +1 @@ +MCPU_AS_LANGUAGE_PATH from_as_language_path; diff --git a/tests/data/lang-base/base.h b/tests/data/lang-base/base.h new file mode 100644 index 0000000..1d8a123 --- /dev/null +++ b/tests/data/lang-base/base.h @@ -0,0 +1 @@ +int from_base_language_include_root; diff --git a/tests/data/lang-base/diff/explicit.h b/tests/data/lang-base/diff/explicit.h new file mode 100644 index 0000000..fa9157f --- /dev/null +++ b/tests/data/lang-base/diff/explicit.h @@ -0,0 +1 @@ +int from_explicit_diff_subdirectory; diff --git a/tests/data/lang-diff/lang.h b/tests/data/lang-diff/lang.h new file mode 100644 index 0000000..54603aa --- /dev/null +++ b/tests/data/lang-diff/lang.h @@ -0,0 +1 @@ +y' = from_diff_language_path; diff --git a/tests/data/lang-path-main.c b/tests/data/lang-path-main.c new file mode 100644 index 0000000..864bc07 --- /dev/null +++ b/tests/data/lang-path-main.c @@ -0,0 +1,6 @@ +#lang "as" +#include "lang.h" +#endlang +#lang "diff" +#include "lang.h" +#endlang diff --git a/tests/data/lang.c b/tests/data/lang.c new file mode 100644 index 0000000..75cdc70 --- /dev/null +++ b/tests/data/lang.c @@ -0,0 +1,9 @@ +int before; +#lang "diff" +y' = omega; +#lang "alg" +f = x + y; +#endlang +z' = y; +#endlang +int after; diff --git a/tests/data/nonucs2.c b/tests/data/nonucs2.c new file mode 100644 index 0000000..d8cc27c --- /dev/null +++ b/tests/data/nonucs2.c @@ -0,0 +1 @@ +int x; /* 😀 */ diff --git a/tests/data/order-main.c b/tests/data/order-main.c new file mode 100644 index 0000000..04cac0c --- /dev/null +++ b/tests/data/order-main.c @@ -0,0 +1 @@ +#include <order.h> diff --git a/tests/data/system/order.h b/tests/data/system/order.h new file mode 100644 index 0000000..8e7f307 --- /dev/null +++ b/tests/data/system/order.h @@ -0,0 +1 @@ +int system_order; diff --git a/tests/data/system/system.h b/tests/data/system/system.h new file mode 100644 index 0000000..c94d2ab --- /dev/null +++ b/tests/data/system/system.h @@ -0,0 +1 @@ +int from_system; diff --git a/tests/data/unbalanced.c b/tests/data/unbalanced.c new file mode 100644 index 0000000..1bf56c1 --- /dev/null +++ b/tests/data/unbalanced.c @@ -0,0 +1,2 @@ +#lang "diff" +y' = 1; diff --git a/tests/data/user/order.h b/tests/data/user/order.h new file mode 100644 index 0000000..f6313ca --- /dev/null +++ b/tests/data/user/order.h @@ -0,0 +1 @@ +int user_order; diff --git a/tests/data/utf8.c b/tests/data/utf8.c new file mode 100644 index 0000000..d5153ea --- /dev/null +++ b/tests/data/utf8.c @@ -0,0 +1,2 @@ +/* UTF-8 source, UCS-2 internal representation. */ +const char *message = "Привет, MCPU"; diff --git a/tests/mcpp-options-test.c b/tests/mcpp-options-test.c new file mode 100644 index 0000000..a0e8311 --- /dev/null +++ b/tests/mcpp-options-test.c @@ -0,0 +1,24 @@ +#include <defs.h> + +int +main( int argc, char **argv ) +{ + mcpp_options options; + const char *p; + int rc = 1; + + mcpp_options_init( &options, argc ); + if( options.actions == NULL ) + return( 1 ); + + p = argv[0] + strlen( argv[0] ); + while( p != argv[0] && p[-1] != '/' ) --p; + options.progname = p; + + if( mcpp_handle_options(&options, argc - 1, argv + 1) == 0 && + mcpp_options_dump(&options, stdout) == 0 ) + rc = 0; + + mcpp_options_free( &options ); + return( rc ); +} diff --git a/tests/t0001-utf8.sh b/tests/t0001-utf8.sh new file mode 100755 index 0000000..6726cfe --- /dev/null +++ b/tests/t0001-utf8.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu +out="${TMPDIR-/tmp}/mcpu-cpp-utf8-$$.out" +trap 'rm -f "$out"' EXIT HUP INT TERM +"$MCPU_CPP" --no-config "$srcdir/data/utf8.c" -o "$out" +grep 'Привет, MCPU' "$out" >/dev/null diff --git a/tests/t0002-lang.sh b/tests/t0002-lang.sh new file mode 100755 index 0000000..1d0102e --- /dev/null +++ b/tests/t0002-lang.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu +out="${TMPDIR-/tmp}/mcpu-cpp-lang-$$.out" +trap 'rm -f "$out"' EXIT HUP INT TERM +"$MCPU_CPP" --no-config "$srcdir/data/lang.c" -o "$out" +grep '#lang "diff"' "$out" >/dev/null +grep '#lang "alg"' "$out" >/dev/null +grep '#endlang' "$out" >/dev/null diff --git a/tests/t0003-include.sh b/tests/t0003-include.sh new file mode 100755 index 0000000..b8ddcec --- /dev/null +++ b/tests/t0003-include.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu +out="${TMPDIR-/tmp}/mcpu-cpp-include-$$.out" +trap 'rm -f "$out"' EXIT HUP INT TERM +"$MCPU_CPP" --no-config -I "$srcdir/data/include" \ + -isystem "$srcdir/data/system" \ + "$srcdir/data/include-main.c" -o "$out" +grep 'from_one' "$out" >/dev/null +grep 'from_system' "$out" >/dev/null +grep 'bridge_c' "$out" >/dev/null +grep "y' = 1" "$out" >/dev/null diff --git a/tests/t0004-config.sh b/tests/t0004-config.sh new file mode 100755 index 0000000..d025d99 --- /dev/null +++ b/tests/t0004-config.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -eu +out="${TMPDIR-/tmp}/mcpu-cpp-config-$$.out" +conf="${TMPDIR-/tmp}/mcpu-cpp-config-$$.conf" +trap 'rm -f "$out" "$conf"' EXIT HUP INT TERM +cat > "$conf" <<EOT +MCPU_CPP_INCLUDE_PATH = $srcdir/data/include; +MCPU_CPP_SYSTEM_INCLUDE_PATH = $srcdir/data/system; +EOT +"$MCPU_CPP" --config-file "$conf" "$srcdir/data/config-main.c" -o "$out" +grep 'from_one' "$out" >/dev/null +grep 'from_system' "$out" >/dev/null diff --git a/tests/t0005-errors.sh b/tests/t0005-errors.sh new file mode 100755 index 0000000..458b4c8 --- /dev/null +++ b/tests/t0005-errors.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -eu +out="${TMPDIR-/tmp}/mcpu-cpp-error-$$.out" +err="${TMPDIR-/tmp}/mcpu-cpp-error-$$.err" +bad="${TMPDIR-/tmp}/mcpu-cpp-nonucs2-$$.c" +trap 'rm -f "$out" "$err" "$bad"' EXIT HUP INT TERM + +if "$MCPU_CPP" --no-config "$srcdir/data/unbalanced.c" -o "$out" 2>"$err"; then + exit 1 +fi +grep 'unbalanced #lang/#endlang' "$err" >/dev/null + +# A valid UTF-8 scalar outside UCS-2 is harmless inside a comment. +"$MCPU_CPP" --no-config "$srcdir/data/nonucs2.c" -o "$out" +grep '^int x;$' "$out" >/dev/null + +# The same scalar in program text is rejected after comment removal. +printf 'int x = \360\237\230\200;\n' > "$bad" +if "$MCPU_CPP" --no-config "$bad" -o "$out" 2>"$err"; then + exit 1 +fi +grep 'character outside UCS-2' "$err" >/dev/null diff --git a/tests/t0006-path-order.sh b/tests/t0006-path-order.sh new file mode 100755 index 0000000..0b9f76e --- /dev/null +++ b/tests/t0006-path-order.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -eu +out="${TMPDIR-/tmp}/mcpu-cpp-order-$$.out" +trap 'rm -f "$out"' EXIT HUP INT TERM +# Deliberately put -isystem first: -I must still have higher semantic priority. +"$MCPU_CPP" --no-config \ + -isystem "$srcdir/data/system" \ + -I "$srcdir/data/user" \ + "$srcdir/data/order-main.c" -o "$out" +grep 'user_order' "$out" >/dev/null +if grep 'system_order' "$out" >/dev/null; then + exit 1 +fi diff --git a/tests/t0007-language-path.sh b/tests/t0007-language-path.sh new file mode 100755 index 0000000..6a75797 --- /dev/null +++ b/tests/t0007-language-path.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -eu +out="${TMPDIR-/tmp}/mcpu-cpp-langpath-$$.out" +conf="${TMPDIR-/tmp}/mcpu-cpp-langpath-$$.conf" +trap 'rm -f "$out" "$conf"' EXIT HUP INT TERM +cat > "$conf" <<EOT +MCPU_CPP_AS_INCLUDE_PATH = $srcdir/data/lang-as; +MCPU_CPP_DIFF_INCLUDE_PATH = $srcdir/data/lang-diff; +EOT +"$MCPU_CPP" --config-file "$conf" "$srcdir/data/lang-path-main.c" -o "$out" +grep 'from_as_language_path' "$out" >/dev/null +grep 'from_diff_language_path' "$out" >/dev/null diff --git a/tests/t0008-comments.sh b/tests/t0008-comments.sh new file mode 100755 index 0000000..26386ba --- /dev/null +++ b/tests/t0008-comments.sh @@ -0,0 +1,41 @@ +#!/bin/sh +set -eu +src="${TMPDIR-/tmp}/mcpu-cpp-comments-$$.c" +out="${TMPDIR-/tmp}/mcpu-cpp-comments-$$.out" +trap 'rm -f "$src" "$out"' EXIT HUP INT TERM +cat > "$src" <<'EOT' +/* +#lang "diff" +*/ +int c_side; +/* comment before directive */ #lang "diff" +y' = 1; +#endlang + // one-line comment + /* + multi-line comment + */ +LEFT/**/RIGHT +int block_tail; /* block tail */ +int line_tail; // line tail +int multi_tail; /* + block tail + */ +EOT +"$MCPU_CPP" --no-config "$src" -o "$out" +grep 'int c_side' "$out" >/dev/null +grep "y' = 1" "$out" >/dev/null +grep '^LEFT RIGHT$' "$out" >/dev/null + +# Comment-only lines must be really empty; no synthetic or leading spaces remain. +if grep '^[[:blank:]][[:blank:]]*$' "$out" >/dev/null; then + exit 1 +fi + +# A comment ending a non-empty line must not leave trailing whitespace. +grep '^int block_tail;$' "$out" >/dev/null +grep '^int line_tail;$' "$out" >/dev/null +grep '^int multi_tail;$' "$out" >/dev/null +if grep '[[:blank:]]$' "$out" >/dev/null; then + exit 1 +fi diff --git a/tests/t0009-text-scanner.sh b/tests/t0009-text-scanner.sh new file mode 100755 index 0000000..773968f --- /dev/null +++ b/tests/t0009-text-scanner.sh @@ -0,0 +1,37 @@ +#!/bin/sh +set -eu +src="${TMPDIR-/tmp}/mcpu-cpp-scan-$$.c" +out="${TMPDIR-/tmp}/mcpu-cpp-scan-$$.out" +err="${TMPDIR-/tmp}/mcpu-cpp-scan-$$.err" +bom="${TMPDIR-/tmp}/mcpu-cpp-bom-$$.c" +nul="${TMPDIR-/tmp}/mcpu-cpp-nul-$$.c" +trap 'rm -f "$src" "$out" "$err" "$bom" "$nul"' EXIT HUP INT TERM + +cat > "$src" <<'EOT' +const char *a = "/* not a comment"; +#lang "diff" +y' = 1; +const char *b = "*/ still not a comment"; +#endlang +const char *c = "// not a comment"; +/* real comment starts here +#lang "alg" +*/ +int c_side; +EOT +"$MCPU_CPP" --no-config "$src" -o "$out" +grep '#lang "diff"' "$out" >/dev/null +grep "y' = 1" "$out" >/dev/null +grep 'int c_side' "$out" >/dev/null + +# UTF-8 BOM is accepted and removed at the external text boundary. +printf '\357\273\277int bom_ok;\n' > "$bom" +"$MCPU_CPP" --no-config "$bom" -o "$out" +grep 'int bom_ok' "$out" >/dev/null + +# Embedded NUL is not text and must never be silently truncated. +printf 'int before;\000int after;\n' > "$nul" +if "$MCPU_CPP" --no-config "$nul" -o "$out" 2>"$err"; then + exit 1 +fi +grep 'NUL character is not allowed' "$err" >/dev/null diff --git a/tests/t0010-language-names.sh b/tests/t0010-language-names.sh new file mode 100755 index 0000000..83d4325 --- /dev/null +++ b/tests/t0010-language-names.sh @@ -0,0 +1,38 @@ +#!/bin/sh +set -eu +dir="${TMPDIR-/tmp}/mcpu-cpp-languages-$$" +mkdir -p "$dir" +trap 'rm -rf "$dir"' EXIT HUP INT TERM + +cat > "$dir/ok.c" <<'EOT' +#lang "diff" +#endlang +#lang "DIFT" +#endlang +#lang "Alg" +#endlang +#lang "aS" +#endlang +#lang "AvM" +#endlang +#lang "acs" +#endlang +EOT +"$MCPU_CPP" --no-config "$dir/ok.c" -o "$dir/ok.out" +grep '^#lang "diff"$' "$dir/ok.out" >/dev/null +grep '^#lang "DIFT"$' "$dir/ok.out" >/dev/null +grep '^#lang "Alg"$' "$dir/ok.out" >/dev/null +grep '^#lang "aS"$' "$dir/ok.out" >/dev/null +grep '^#lang "AvM"$' "$dir/ok.out" >/dev/null +grep '^#lang "acs"$' "$dir/ok.out" >/dev/null + +for lang in 0 c vasm unknown +do + printf '#lang "%s"\n#endlang\n' "$lang" > "$dir/bad.c" + if "$MCPU_CPP" --no-config "$dir/bad.c" -o "$dir/bad.out" 2> "$dir/bad.err" + then + echo "#lang \"$lang\" unexpectedly accepted" >&2 + exit 1 + fi + grep "unknown language '$lang'" "$dir/bad.err" >/dev/null +done diff --git a/tests/t0011-base-include-path.sh b/tests/t0011-base-include-path.sh new file mode 100755 index 0000000..f862e88 --- /dev/null +++ b/tests/t0011-base-include-path.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -eu +dir="${TMPDIR-/tmp}/mcpu-cpp-basepath-$$" +mkdir -p "$dir" +trap 'rm -rf "$dir"' EXIT HUP INT TERM +cat > "$dir/config" <<EOT +MCPU_CPP_INCLUDE_PATH = $srcdir/data/lang-base; +EOT +"$MCPU_CPP" --config-file "$dir/config" "$srcdir/data/base-path-main.c" -o "$dir/base.out" +grep 'from_base_language_include_root' "$dir/base.out" >/dev/null +cat > "$dir/as.c" <<'EOT' +#lang "as" +#include <base.h> +#include <diff/explicit.h> +#endlang +EOT +"$MCPU_CPP" --config-file "$dir/config" "$dir/as.c" -o "$dir/as.out" +grep 'from_base_language_include_root' "$dir/as.out" >/dev/null +grep 'from_explicit_diff_subdirectory' "$dir/as.out" >/dev/null diff --git a/tests/t0012-user-config-path.sh b/tests/t0012-user-config-path.sh new file mode 100755 index 0000000..54b6e64 --- /dev/null +++ b/tests/t0012-user-config-path.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -eu +dir="${TMPDIR-/tmp}/mcpu-cpp-userconf-$$" +mkdir -p "$dir/home/.mcpu" "$dir/include" "$dir/system/as" +trap 'rm -rf "$dir"' EXIT HUP INT TERM +cat > "$dir/home/.mcpu/mcpu-cpp.conf" <<EOT +MCPU_CPP_INCLUDE_PATH = $dir/include; +MCPU_CPP_SYSTEM_INCLUDE_PATH = $dir/system; +EOT +cat > "$dir/include/userconf.h" <<'EOT' +int from_user_mcpu_configuration; +EOT +cat > "$dir/system/as/system-userconf.h" <<'EOT' +int from_user_system_root; +EOT +cat > "$dir/main.c" <<'EOT' +#include <userconf.h> +#lang "as" +#include <system-userconf.h> +#endlang +EOT +HOME="$dir/home" "$MCPU_CPP" "$dir/main.c" -o "$dir/out" +grep 'from_user_mcpu_configuration' "$dir/out" >/dev/null +grep 'from_user_system_root' "$dir/out" >/dev/null +HOME="$dir/home" "$MCPU_CPP" -dconfig > "$dir/config.out" +grep "^MCPU_CPP_SYSTEM_INCLUDE_PATH = $dir/system;$" "$dir/config.out" >/dev/null diff --git a/tests/t0013-include-precedence.sh b/tests/t0013-include-precedence.sh new file mode 100755 index 0000000..0bfc1e5 --- /dev/null +++ b/tests/t0013-include-precedence.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -eu +dir="${TMPDIR-/tmp}/mcpu-cpp-precedence-$$" +mkdir -p "$dir" +trap 'rm -rf "$dir"' EXIT HUP INT TERM +cat > "$dir/config" <<EOT +MCPU_CPP_INCLUDE_PATH = $srcdir/data/common-precedence; +MCPU_CPP_AS_INCLUDE_PATH = $srcdir/data/lang-as-precedence; +EOT +cat > "$dir/main.c" <<'EOT' +#include <same.h> +#lang "as" +#include <same.h> +#endlang +EOT +"$MCPU_CPP" --config-file "$dir/config" "$dir/main.c" -o "$dir/out" +grep 'from_common_include_root' "$dir/out" >/dev/null +grep 'from_as_specific_include_path' "$dir/out" >/dev/null diff --git a/tests/t0014-preprocessing-phases.sh b/tests/t0014-preprocessing-phases.sh new file mode 100755 index 0000000..5cc98c8 --- /dev/null +++ b/tests/t0014-preprocessing-phases.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -eu +src="${TMPDIR-/tmp}/mcpu-cpp-phases-$$.c" +out="${TMPDIR-/tmp}/mcpu-cpp-phases-$$.out" +trap 'rm -f "$src" "$out"' EXIT HUP INT TERM +cat > "$src" <<'EOT' +#defi\ +ne VALUE 10\ +20 +/* comment */ VALUE // trailing comment +const char *s = "VALUE /* not a comment */"; +EOT +"$MCPU_CPP" --no-config "$src" -o "$out" +grep '1020' "$out" >/dev/null +grep 'VALUE /\* not a comment \*/' "$out" >/dev/null +if grep 'trailing comment' "$out" >/dev/null; then + exit 1 +fi +if grep '/\* comment \*/' "$out" >/dev/null; then + exit 1 +fi diff --git a/tests/t0015-object-macros.sh b/tests/t0015-object-macros.sh new file mode 100755 index 0000000..a59bd23 --- /dev/null +++ b/tests/t0015-object-macros.sh @@ -0,0 +1,27 @@ +#!/bin/sh +set -eu +src="${TMPDIR-/tmp}/mcpu-cpp-macro-$$.c" +out="${TMPDIR-/tmp}/mcpu-cpp-macro-$$.out" +trap 'rm -f "$src" "$out"' EXIT HUP INT TERM +cat > "$src" <<'EOT' +#define A B +#define B 42 +#define EMPTY +A +x EMPTY y +"A B" +#define SELF SELF +SELF +#undef B +A +#lang "diff" +y' = A; +#endlang +EOT +"$MCPU_CPP" --no-config "$src" -o "$out" +grep '^42$' "$out" >/dev/null +grep '^x y$' "$out" >/dev/null +grep '^"A B"$' "$out" >/dev/null +grep '^SELF$' "$out" >/dev/null +grep '^B$' "$out" >/dev/null +grep "y' = B;" "$out" >/dev/null diff --git a/tests/t0016-macro-include.sh b/tests/t0016-macro-include.sh new file mode 100755 index 0000000..0c1b56e --- /dev/null +++ b/tests/t0016-macro-include.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-minclude-$$" +mkdir -p "$base/inc" +trap 'rm -rf "$base"' EXIT HUP INT TERM +cat > "$base/main.c" <<'EOT' +#define HEADER <macro.h> +#include HEADER +VALUE +EOT +cat > "$base/inc/macro.h" <<'EOT' +#define VALUE 77 +EOT +"$MCPU_CPP" --no-config -I "$base/inc" "$base/main.c" -o "$base/out" +grep '^77$' "$base/out" >/dev/null +grep "^# 3 \"$base/main.c\" 2$" "$base/out" >/dev/null diff --git a/tests/t0017-macro-errors.sh b/tests/t0017-macro-errors.sh new file mode 100755 index 0000000..f54f74c --- /dev/null +++ b/tests/t0017-macro-errors.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -eu +src="${TMPDIR-/tmp}/mcpu-cpp-macro-errors-$$.c" +out="${TMPDIR-/tmp}/mcpu-cpp-macro-errors-$$.out" +err="${TMPDIR-/tmp}/mcpu-cpp-macro-errors-$$.err" +trap 'rm -f "$src" "$out" "$err"' EXIT HUP INT TERM +cat > "$src" <<'EOT' +#define +EOT +if "$MCPU_CPP" --no-config "$src" -o "$out" 2>"$err"; then + exit 1 +fi +grep 'macro name expected after #define' "$err" >/dev/null diff --git a/tests/t0018-include-lexing.sh b/tests/t0018-include-lexing.sh new file mode 100755 index 0000000..ed1561d --- /dev/null +++ b/tests/t0018-include-lexing.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-include-lex-$$" +mkdir -p "$base/inc/x" +trap 'rm -rf "$base"' EXIT HUP INT TERM +cat > "$base/main.c" <<'EOT' +#include \ +<x/*y> +AFTER +EOT +cat > "$base/inc/x/*y" <<'EOT' +INSIDE +EOT +"$MCPU_CPP" --no-config -I "$base/inc" "$base/main.c" -o "$base/out" +grep '^INSIDE$' "$base/out" >/dev/null +grep '^AFTER$' "$base/out" >/dev/null +grep "^# 3 \"$base/main.c\" 2$" "$base/out" >/dev/null diff --git a/tests/t0019-recursive-macro.sh b/tests/t0019-recursive-macro.sh new file mode 100755 index 0000000..ffba144 --- /dev/null +++ b/tests/t0019-recursive-macro.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -eu +src="${TMPDIR-/tmp}/mcpu-cpp-recursive-$$.c" +out="${TMPDIR-/tmp}/mcpu-cpp-recursive-$$.out" +trap 'rm -f "$src" "$out"' EXIT HUP INT TERM +cat > "$src" <<'EOT' +int foo; +#define foo (4 + foo) +f = foo; +EOT +"$MCPU_CPP" --no-config "$src" -o "$out" +grep '^int foo;$' "$out" >/dev/null +grep '^f = (4 + foo);$' "$out" >/dev/null diff --git a/tests/t0020-function-macros.sh b/tests/t0020-function-macros.sh new file mode 100755 index 0000000..aba3fc5 --- /dev/null +++ b/tests/t0020-function-macros.sh @@ -0,0 +1,42 @@ +#!/bin/sh +set -eu +src="${TMPDIR-/tmp}/mcpu-cpp-fmacro-$$.c" +out="${TMPDIR-/tmp}/mcpu-cpp-fmacro-$$.out" +trap 'rm -f "$src" "$out"' EXIT HUP INT TERM +cat > "$src" <<'EOT' +#define min(X, Y) ((X) < (Y) ? (X) : (Y)) +#define A 7 +#define ZERO() zero-text +#define INNER(X) [X] +#define OUTER(X, Y) INNER(X) + INNER(Y) +#define OBJECT (argument) +#define WRAP(X) <X> +#define TWICE(X) (X) + \ +(X) +min(1, 2) +min(A, 3) +min(min(a, b), c) +OUTER(A, min(4, 5)) +ZERO () +ZERO +OBJECT +WRAP("a,b") +TWICE(3) +"min(A, 3)" +#lang "diff" +#define DERIV(X) X' + X +DERIV(y) +#endlang +EOT +"$MCPU_CPP" --no-config "$src" -o "$out" +grep '^((1) < (2) ? (1) : (2))$' "$out" >/dev/null +grep '^((7) < (3) ? (7) : (3))$' "$out" >/dev/null +grep '^((((a) < (b) ? (a) : (b))) < (c) ? (((a) < (b) ? (a) : (b))) : (c))$' "$out" >/dev/null +grep '^\[7\] + \[((4) < (5) ? (4) : (5))\]$' "$out" >/dev/null +grep '^zero-text$' "$out" >/dev/null +grep '^ZERO$' "$out" >/dev/null +grep '^(argument)$' "$out" >/dev/null +grep '^<"a,b">$' "$out" >/dev/null +grep '^(3) + (3)$' "$out" >/dev/null +grep '^"min(A, 3)"$' "$out" >/dev/null +grep "^y' + y$" "$out" >/dev/null diff --git a/tests/t0021-function-macro-errors.sh b/tests/t0021-function-macro-errors.sh new file mode 100755 index 0000000..2353b35 --- /dev/null +++ b/tests/t0021-function-macro-errors.sh @@ -0,0 +1,89 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-fmacro-errors-$$" +trap 'rm -f "$base".*' EXIT HUP INT TERM + +cat > "$base.dup.c" <<'EOT' +#define F(X, X) X +EOT +if "$MCPU_CPP" --no-config "$base.dup.c" -o "$base.out" 2>"$base.err"; then + echo "duplicate parameter name accepted" >&2 + exit 1 +fi +grep "duplicate argument name 'X'" "$base.err" >/dev/null + +cat > "$base.few.c" <<'EOT' +#define F(X, Y) X + Y +F(1) +EOT +if "$MCPU_CPP" --no-config "$base.few.c" -o "$base.out" 2>"$base.err"; then + echo "too few macro arguments accepted" >&2 + exit 1 +fi +grep "used with too few arguments" "$base.err" >/dev/null + +cat > "$base.many.c" <<'EOT' +#define F(X) X +F(1, 2) +EOT +if "$MCPU_CPP" --no-config "$base.many.c" -o "$base.out" 2>"$base.err"; then + echo "too many macro arguments accepted" >&2 + exit 1 +fi +grep "used with too many arguments" "$base.err" >/dev/null + +cat > "$base.unterm.c" <<'EOT' +#define F(X) X +F(1 +EOT +if "$MCPU_CPP" --no-config "$base.unterm.c" -o "$base.out" 2>"$base.err"; then + echo "unterminated macro call accepted" >&2 + exit 1 +fi +grep "unterminated argument list" "$base.err" >/dev/null + +cat > "$base.concat-first.c" <<'EOT' +#define CAT(X, Y) ## X +EOT +if "$MCPU_CPP" --no-config "$base.concat-first.c" -o "$base.out" 2>"$base.err"; then + echo "leading ## in replacement list accepted" >&2 + exit 1 +fi +grep "'##' cannot appear at the beginning of a macro replacement list" "$base.err" >/dev/null + +cat > "$base.concat-last.c" <<'EOT' +#define CAT(X, Y) Y ## +EOT +if "$MCPU_CPP" --no-config "$base.concat-last.c" -o "$base.out" 2>"$base.err"; then + echo "trailing ## in replacement list accepted" >&2 + exit 1 +fi +grep "'##' cannot appear at the end of a macro replacement list" "$base.err" >/dev/null + +cat > "$base.sharp-name.c" <<'EOT' +#define S(X) #Y +EOT +if "$MCPU_CPP" --no-config "$base.sharp-name.c" -o "$base.out" 2>"$base.err"; then + echo "stringification of a non-parameter accepted" >&2 + exit 1 +fi +grep "'#' operator should be followed by a macro argument name" "$base.err" >/dev/null + +cat > "$base.sharp-end.c" <<'EOT' +#define S(X) # +EOT +if "$MCPU_CPP" --no-config "$base.sharp-end.c" -o "$base.out" 2>"$base.err"; then + echo "unterminated stringification operator accepted" >&2 + exit 1 +fi +grep "'#' operator is not followed by a macro argument name" "$base.err" >/dev/null + +cat > "$base.bracket.c" <<'EOT' +#define F(X) X +F(array[x = y, x + 1]) +EOT +if "$MCPU_CPP" --no-config "$base.bracket.c" -o "$base.out" 2>"$base.err"; then + echo "comma in brackets incorrectly protected macro argument" >&2 + exit 1 +fi +grep "used with too many arguments" "$base.err" >/dev/null diff --git a/tests/t0022-predefined-macros.sh b/tests/t0022-predefined-macros.sh new file mode 100755 index 0000000..960e1e8 --- /dev/null +++ b/tests/t0022-predefined-macros.sh @@ -0,0 +1,56 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-predef-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/main.c" <<'EOT' +root_file = __FILE__; +root_base = __BASE_FILE__; +root_level = __INCLUDE_LEVEL__; +#define HERE() __LINE__ +root_line = HERE(); +cpp_version = __MCPU_CPP_VERSION__; +old_version_name = __VERSION__; +date = __DATE__; +time = __TIME__; +#include "one.h" +after_line = __LINE__; +EOT + +cat > "$base/one.h" <<'EOT' +one_file = __FILE__; +one_base = __BASE_FILE__; +one_level = __INCLUDE_LEVEL__; +one_line = __LINE__; +#include "two.h" +EOT + +cat > "$base/two.h" <<'EOT' +two_file = __FILE__; +two_base = __BASE_FILE__; +two_level = __INCLUDE_LEVEL__; +two_line = __LINE__; +EOT + +"$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" + +grep -F "root_file = \"$base/main.c\";" "$base/out" >/dev/null +grep -F "root_base = \"$base/main.c\";" "$base/out" >/dev/null +grep '^root_level = 0;$' "$base/out" >/dev/null +grep '^root_line = 5;$' "$base/out" >/dev/null +grep '^cpp_version = "1\.0\.2";$' "$base/out" >/dev/null +grep '^old_version_name = __VERSION__;$' "$base/out" >/dev/null +grep '^date = "[A-Z][a-z][a-z] [0-9] [0-9][0-9][0-9][0-9]";$\|^date = "[A-Z][a-z][a-z] [12][0-9] [0-9][0-9][0-9][0-9]";$\|^date = "[A-Z][a-z][a-z] 3[01] [0-9][0-9][0-9][0-9]";$' "$base/out" >/dev/null +grep '^time = "[0-2][0-9]:[0-5][0-9]:[0-5][0-9]";$' "$base/out" >/dev/null + +grep -F "one_file = \"$base/one.h\";" "$base/out" >/dev/null +grep -F "one_base = \"$base/main.c\";" "$base/out" >/dev/null +grep '^one_level = 1;$' "$base/out" >/dev/null +grep '^one_line = 4;$' "$base/out" >/dev/null + +grep -F "two_file = \"$base/two.h\";" "$base/out" >/dev/null +grep -F "two_base = \"$base/main.c\";" "$base/out" >/dev/null +grep '^two_level = 2;$' "$base/out" >/dev/null +grep '^two_line = 4;$' "$base/out" >/dev/null +grep '^after_line = 11;$' "$base/out" >/dev/null diff --git a/tests/t0023-predefined-redefine.sh b/tests/t0023-predefined-redefine.sh new file mode 100755 index 0000000..b30a1d9 --- /dev/null +++ b/tests/t0023-predefined-redefine.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -eu +src="${TMPDIR-/tmp}/mcpu-cpp-predef-redef-$$.c" +out="${TMPDIR-/tmp}/mcpu-cpp-predef-redef-$$.out" +err="${TMPDIR-/tmp}/mcpu-cpp-predef-redef-$$.err" +trap 'rm -f "$src" "$out" "$err"' EXIT HUP INT TERM +cat > "$src" <<'EOT' +before = __LINE__; +#define __LINE__ 77 +after = __LINE__; +#undef __LINE__ +literal = __LINE__; +"__FILE__ __LINE__ __MCPU_CPP_VERSION__" +EOT +"$MCPU_CPP" --no-config "$src" -o "$out" 2>"$err" +grep '^before = 1;$' "$out" >/dev/null +grep '^literal = __LINE__;$' "$out" >/dev/null +grep '^after = 77;$' "$out" >/dev/null +grep '^"__FILE__ __LINE__ __MCPU_CPP_VERSION__"$' "$out" >/dev/null +grep "warning: macro '__LINE__' redefined" "$err" >/dev/null diff --git a/tests/t0024-abi-predefined.sh b/tests/t0024-abi-predefined.sh new file mode 100755 index 0000000..984f0a2 --- /dev/null +++ b/tests/t0024-abi-predefined.sh @@ -0,0 +1,191 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-abi-predef-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/main.c" <<'EOT' +arch = _ARCH_MCPU; +cpp_version = __MCPU_CPP_VERSION__; +old_version = __VERSION__; +byte_order = __BYTE_ORDER__; +mcpu_byte_order = __MCPU_BYTE_ORDER__; +word_order = __MCPU_WORD_ORDER__; +old_float_word_order = __FLOAT_WORD_ORDER__; +register_width = __MCPU_MACHINE_REGISTER_WIDTH__; +real_io_limit = __MCPU_REAL_IO_LIMIT__; +math_fn_limit = __MCPU_MATH_FN_LIMIT__; +pointer_width = __MCPU_POINTER_WIDTH__; +int_max_width = __MCPU_INT_MAX_WIDTH__; +real_max_width = __MCPU_REAL_MAX_WIDTH__; +complex_max_width = __MCPU_COMPLEX_MAX_WIDTH__; +size_type = __MCPU_SIZE_TYPE__; +size_width = __MCPU_SIZE_WIDTH__; +size_sizeof = __MCPU_SIZEOF_SIZE__; +size_max = __MCPU_SIZE_MAX__; +ssize_type = __MCPU_SSIZE_TYPE__; +ssize_width = __MCPU_SSIZE_WIDTH__; +ssize_sizeof = __MCPU_SIZEOF_SSIZE__; +ssize_max = __MCPU_SSIZE_MAX__; +ptrdiff_type = __PTRDIFF_TYPE__; +ptrdiff_width = __PTRDIFF_WIDTH__; +ptrdiff_max = __PTRDIFF_MAX__; +sizeof_ptrdiff = __SIZEOF_PTRDIFF__; +old_sizeof_ptrdiff_t = __SIZEOF_PTRDIFF_T__; +intptr_type = __INTPTR_TYPE__; +uintptr_type = __UINTPTR_TYPE__; +intptr_width = __INTPTR_WIDTH__; +uintptr_width = __UINTPTR_WIDTH__; +intptr_max = __INTPTR_MAX__; +uintptr_max = __UINTPTR_MAX__; +char8_type = __CHAR8_TYPE__; +char16_type = __CHAR16_TYPE__; +char8_width = __CHAR8_WIDTH__; +char16_width = __CHAR16_WIDTH__; +sizeof_char8 = __SIZEOF_CHAR8__; +sizeof_char16 = __SIZEOF_CHAR16__; +register_prefix = [__REGISTER_PREFIX__]; +local_label_prefix = [__LOCAL_LABEL_PREFIX__]; +user_label_prefix = [__USER_LABEL_PREFIX__]; +immediate_prefix = [__IMMEDIATE_PREFIX__]; +int128_type = __INT128_TYPE__; +uint128_type = __UINT128_TYPE__; +int128_width = __INT128_WIDTH__; +int128_decimal = __INT128_DECIMAL_DIG__; +uint128_decimal = __UINT128_DECIMAL_DIG__; +sizeof_int128 = __SIZEOF_INT128__; +sizeof_uint128 = __SIZEOF_UINT128__; +int128_max = __INT128_MAX__; +int128_min_must_not_exist = __INT128_MIN__; +uint128_max = __UINT128_MAX__; +int1024_type = __INT1024_TYPE__; +int1024_width = __INT1024_WIDTH__; +int1024_max_must_not_exist = __INT1024_MAX__; +int1024_decimal = __INT1024_DECIMAL_DIG__; +sizeof_int1024 = __SIZEOF_INT1024__; +real128_type = __REAL128_TYPE__; +complex128_type = __COMPLEX128_TYPE__; +real128_width = __REAL128_WIDTH__; +complex128_width = __COMPLEX128_WIDTH__; +sizeof_real128 = __SIZEOF_REAL128__; +sizeof_real128_exp = __SIZEOF_REAL128_EXP__; +real128_max_strlen = __REAL128_MAX_STRLEN__; +sizeof_complex128 = __SIZEOF_COMPLEX128__; +real128_mant = __REAL128_MANT_DIG__; +real128_decimal = __REAL128_DECIMAL_DIG__; +real128_old_dig_must_not_exist = __REAL128_DIG__; +real128_max = __REAL128_MAX__; +real128_min = __REAL128_MIN__; +real128_epsilon = __REAL128_EPSILON__; +real128_max_exp = __REAL128_MAX_EXP__; +real128_min_exp = __REAL128_MIN_EXP__; +real1024_type = __REAL1024_TYPE__; +real1024_width = __REAL1024_WIDTH__; +real1024_max_must_not_exist = __REAL1024_MAX__; +real1024_mant = __REAL1024_MANT_DIG__; +real1024_decimal = __REAL1024_DECIMAL_DIG__; +sizeof_real1024 = __SIZEOF_REAL1024__; +sizeof_real1024_exp = __SIZEOF_REAL1024_EXP__; +real1024_max_strlen = __REAL1024_MAX_STRLEN__; +complex1024_type = __COMPLEX1024_TYPE__; +complex1024_width = __COMPLEX1024_WIDTH__; +sizeof_complex1024 = __SIZEOF_COMPLEX1024__; +int65536_type = __INT65536_TYPE__; +int65536_width = __INT65536_WIDTH__; +sizeof_int65536 = __SIZEOF_INT65536__; +char_type_must_not_exist = __CHAR_TYPE__; +wchar_type_must_not_exist = __WCHAR_TYPE__; +EOT + +"$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" + +grep '^arch = 1;$' "$base/out" >/dev/null +grep '^cpp_version = "1\.0\.2";$' "$base/out" >/dev/null +grep '^old_version = __VERSION__;$' "$base/out" >/dev/null +grep '^byte_order = \(1234\|4321\);$' "$base/out" >/dev/null +grep '^mcpu_byte_order = \(1234\|4321\);$' "$base/out" >/dev/null +grep '^word_order = \(1234\|4321\);$' "$base/out" >/dev/null +grep '^old_float_word_order = __FLOAT_WORD_ORDER__;$' "$base/out" >/dev/null +grep '^register_width = 64;$' "$base/out" >/dev/null +grep '^pointer_width = 64;$' "$base/out" >/dev/null +grep '^int_max_width = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^real_max_width = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^complex_max_width = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^size_type = uint[0-9][0-9]*;$' "$base/out" >/dev/null +grep '^size_width = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^size_sizeof = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^size_max = 0x[0-9a-f][0-9a-f]*;$' "$base/out" >/dev/null +grep '^ssize_type = int[0-9][0-9]*;$' "$base/out" >/dev/null +grep '^ssize_width = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^ssize_sizeof = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^ssize_max = 0x[0-9a-f][0-9a-f]*;$' "$base/out" >/dev/null +grep '^ptrdiff_type = int64;$' "$base/out" >/dev/null +grep '^ptrdiff_width = 64;$' "$base/out" >/dev/null +grep '^ptrdiff_max = 0x7fffffffffffffff;$' "$base/out" >/dev/null +grep '^sizeof_ptrdiff = 8;$' "$base/out" >/dev/null +grep '^old_sizeof_ptrdiff_t = __SIZEOF_PTRDIFF_T__;$' "$base/out" >/dev/null +grep '^intptr_type = int64;$' "$base/out" >/dev/null +grep '^uintptr_type = uint64;$' "$base/out" >/dev/null +grep '^intptr_width = 64;$' "$base/out" >/dev/null +grep '^uintptr_width = 64;$' "$base/out" >/dev/null +grep '^intptr_max = 0x7fffffffffffffff;$' "$base/out" >/dev/null +grep '^uintptr_max = 0xffffffffffffffff;$' "$base/out" >/dev/null +grep '^char8_type = char8;$' "$base/out" >/dev/null +grep '^char16_type = char16;$' "$base/out" >/dev/null +grep '^char8_width = 8;$' "$base/out" >/dev/null +grep '^char16_width = 16;$' "$base/out" >/dev/null +grep '^sizeof_char8 = 1;$' "$base/out" >/dev/null +grep '^sizeof_char16 = 2;$' "$base/out" >/dev/null +grep '^register_prefix = \[\];$' "$base/out" >/dev/null +grep '^local_label_prefix = \[\];$' "$base/out" >/dev/null +grep '^user_label_prefix = \[\];$' "$base/out" >/dev/null +grep '^immediate_prefix = \[\];$' "$base/out" >/dev/null +grep '^int128_type = int128;$' "$base/out" >/dev/null +grep '^uint128_type = uint128;$' "$base/out" >/dev/null +grep '^int128_width = 128;$' "$base/out" >/dev/null +grep '^int128_decimal = 39;$' "$base/out" >/dev/null +grep '^uint128_decimal = 39;$' "$base/out" >/dev/null +grep '^sizeof_int128 = 16;$' "$base/out" >/dev/null +grep '^sizeof_uint128 = 16;$' "$base/out" >/dev/null +grep '^sizeof_real128_exp = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^real128_max_strlen = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^int128_max = 0x7fffffffffffffffffffffffffffffff;$' "$base/out" >/dev/null +grep '^int128_min_must_not_exist = __INT128_MIN__;$' "$base/out" >/dev/null +grep '^uint128_max = 0xffffffffffffffffffffffffffffffff;$' "$base/out" >/dev/null +grep '^int1024_type = int1024;$' "$base/out" >/dev/null +grep '^int1024_width = 1024;$' "$base/out" >/dev/null +grep '^int1024_max_must_not_exist = __INT1024_MAX__;$' "$base/out" >/dev/null +grep '^int1024_decimal = 308;$' "$base/out" >/dev/null +grep '^sizeof_int1024 = 128;$' "$base/out" >/dev/null +grep '^real128_type = real128;$' "$base/out" >/dev/null +grep '^complex128_type = complex128;$' "$base/out" >/dev/null +grep '^real128_width = 128;$' "$base/out" >/dev/null +grep '^complex128_width = 128;$' "$base/out" >/dev/null +grep '^sizeof_real128 = 16;$' "$base/out" >/dev/null +grep '^sizeof_complex128 = 32;$' "$base/out" >/dev/null +grep '^real128_mant = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^real128_decimal = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^real128_old_dig_must_not_exist = __REAL128_DIG__;$' "$base/out" >/dev/null +grep '^real128_max = [0-9][0-9.]*e+[0-9][0-9]*;$' "$base/out" >/dev/null +grep '^real128_min = [0-9][0-9.]*e-[0-9][0-9]*;$' "$base/out" >/dev/null +grep '^real128_epsilon = [0-9][0-9.]*e-[0-9][0-9]*;$' "$base/out" >/dev/null +grep '^real128_max_exp = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^real128_min_exp = -[1-9][0-9]*;$' "$base/out" >/dev/null +grep '^real1024_type = real1024;$' "$base/out" >/dev/null +grep '^real1024_width = 1024;$' "$base/out" >/dev/null +grep '^real1024_max_must_not_exist = __REAL1024_MAX__;$' "$base/out" >/dev/null +grep '^real1024_mant = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^real1024_decimal = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^sizeof_real1024 = 128;$' "$base/out" >/dev/null +grep '^sizeof_real1024_exp = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^real1024_max_strlen = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^complex1024_type = complex1024;$' "$base/out" >/dev/null +grep '^complex1024_width = 1024;$' "$base/out" >/dev/null +grep '^sizeof_complex1024 = 256;$' "$base/out" >/dev/null +grep '^int65536_type = int65536;$' "$base/out" >/dev/null +grep '^int65536_width = 65536;$' "$base/out" >/dev/null +grep '^sizeof_int65536 = 8192;$' "$base/out" >/dev/null +grep '^real_io_limit = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^math_fn_limit = [1-9][0-9]*;$' "$base/out" >/dev/null +grep '^char_type_must_not_exist = __CHAR_TYPE__;$' "$base/out" >/dev/null +grep '^wchar_type_must_not_exist = __WCHAR_TYPE__;$' "$base/out" >/dev/null diff --git a/tests/t0025-dump-macros.sh b/tests/t0025-dump-macros.sh new file mode 100755 index 0000000..ea0ede5 --- /dev/null +++ b/tests/t0025-dump-macros.sh @@ -0,0 +1,99 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-dm-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +"$MCPU_CPP" --no-config -dMP < /dev/null > "$base/macros" + +test -s "$base/macros" +LC_ALL=C sort -c "$base/macros" +grep '^#define _ARCH_MCPU 1$' "$base/macros" >/dev/null +grep '^#define __MCPU_CPP_VERSION__ "1\.0\.2"$' "$base/macros" >/dev/null +grep '^#define __MCPU_MACHINE_REGISTER_WIDTH__ 64$' "$base/macros" >/dev/null +grep '^#define __MCPU_REAL_IO_LIMIT__ [0-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_MATH_FN_LIMIT__ [0-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_BYTE_ORDER__ __ORDER_\(LITTLE\|BIG\)_ENDIAN__$' "$base/macros" >/dev/null +grep '^#define __MCPU_WORD_ORDER__ __ORDER_\(LITTLE\|BIG\)_ENDIAN__$' "$base/macros" >/dev/null +grep '^#define __BYTE_ORDER__ __MCPU_BYTE_ORDER__$' "$base/macros" >/dev/null +grep '^#define __MCPU_INT_MAX_WIDTH__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_REAL_MAX_WIDTH__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_COMPLEX_MAX_WIDTH__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __REGISTER_PREFIX__$' "$base/macros" >/dev/null +grep '^#define __USER_LABEL_PREFIX__$' "$base/macros" >/dev/null +grep '^#define __MCPU_SIZE_TYPE__ uint[0-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SIZE_WIDTH__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SIZEOF_SIZE__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SIZE_MAX__ 0x[0-9a-f][0-9a-f]*$' "$base/macros" >/dev/null +grep '^#define __PTRDIFF_TYPE__ int64$' "$base/macros" >/dev/null +grep '^#define __PTRDIFF_WIDTH__ 64$' "$base/macros" >/dev/null +grep '^#define __PTRDIFF_MAX__ 0x7fffffffffffffff$' "$base/macros" >/dev/null +grep '^#define __SIZEOF_PTRDIFF__ 8$' "$base/macros" >/dev/null +grep '^#define __INTPTR_TYPE__ int64$' "$base/macros" >/dev/null +grep '^#define __INTPTR_WIDTH__ 64$' "$base/macros" >/dev/null +grep '^#define __INTPTR_MAX__ 0x7fffffffffffffff$' "$base/macros" >/dev/null +grep '^#define __MCPU_SSIZE_TYPE__ int[0-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SSIZE_WIDTH__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SIZEOF_SSIZE__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SSIZE_MAX__ 0x[0-9a-f][0-9a-f]*$' "$base/macros" >/dev/null +grep '^#define __UINTPTR_TYPE__ uint64$' "$base/macros" >/dev/null +grep '^#define __UINTPTR_WIDTH__ 64$' "$base/macros" >/dev/null +grep '^#define __UINTPTR_MAX__ 0xffffffffffffffff$' "$base/macros" >/dev/null +grep '^#define __CHAR8_TYPE__ char8$' "$base/macros" >/dev/null +grep '^#define __CHAR16_TYPE__ char16$' "$base/macros" >/dev/null +grep '^#define __SIZEOF_CHAR8__ 1$' "$base/macros" >/dev/null +grep '^#define __SIZEOF_CHAR16__ 2$' "$base/macros" >/dev/null +grep '^#define __INT128_DECIMAL_DIG__ 39$' "$base/macros" >/dev/null +grep '^#define __UINT128_DECIMAL_DIG__ 39$' "$base/macros" >/dev/null +grep '^#define __REAL128_DECIMAL_DIG__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __REAL128_MANT_DIG__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __SIZEOF_REAL128_EXP__ 4$' "$base/macros" >/dev/null +grep '^#define __REAL128_MAX_STRLEN__ 60$' "$base/macros" >/dev/null +grep '^#define __REAL128_MAX__ [0-9][0-9.]*e+[0-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __INT1024_TYPE__ int1024$' "$base/macros" >/dev/null +grep '^#define __INT1024_WIDTH__ 1024$' "$base/macros" >/dev/null +grep '^#define __SIZEOF_INT1024__ 128$' "$base/macros" >/dev/null +grep '^#define __INT1024_DECIMAL_DIG__ 308$' "$base/macros" >/dev/null +grep '^#define __UINT1024_DECIMAL_DIG__ 309$' "$base/macros" >/dev/null +grep '^#define __INT65536_TYPE__ int65536$' "$base/macros" >/dev/null +grep '^#define __INT65536_WIDTH__ 65536$' "$base/macros" >/dev/null +grep '^#define __SIZEOF_INT65536__ 8192$' "$base/macros" >/dev/null +grep '^#define __REAL1024_TYPE__ real1024$' "$base/macros" >/dev/null +grep '^#define __REAL1024_WIDTH__ 1024$' "$base/macros" >/dev/null +grep '^#define __SIZEOF_REAL1024__ 128$' "$base/macros" >/dev/null +grep '^#define __REAL1024_DECIMAL_DIG__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __REAL1024_MANT_DIG__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __COMPLEX128_TYPE__ complex128$' "$base/macros" >/dev/null +grep '^#define __COMPLEX128_WIDTH__ 128$' "$base/macros" >/dev/null +grep '^#define __SIZEOF_COMPLEX128__ 32$' "$base/macros" >/dev/null +real_limit=`sed -n 's/^#define __MCPU_REAL_IO_LIMIT__ \([0-9][0-9]*\)$/\1/p' "$base/macros"` +test -n "$real_limit" +real_size=`expr "$real_limit" / 8` +complex_size=`expr "$real_limit" / 4` +grep "^#define __REAL${real_limit}_TYPE__ real${real_limit}$" "$base/macros" >/dev/null +grep "^#define __REAL${real_limit}_WIDTH__ ${real_limit}$" "$base/macros" >/dev/null +grep "^#define __SIZEOF_REAL${real_limit}__ ${real_size}$" "$base/macros" >/dev/null +grep "^#define __SIZEOF_REAL${real_limit}_EXP__ [1-9][0-9]*$" "$base/macros" >/dev/null +grep "^#define __REAL${real_limit}_MAX_STRLEN__ [1-9][0-9]*$" "$base/macros" >/dev/null +grep "^#define __REAL${real_limit}_DECIMAL_DIG__ [1-9][0-9]*$" "$base/macros" >/dev/null +grep "^#define __REAL${real_limit}_MANT_DIG__ [1-9][0-9]*$" "$base/macros" >/dev/null +grep "^#define __COMPLEX${real_limit}_TYPE__ complex${real_limit}$" "$base/macros" >/dev/null +grep "^#define __COMPLEX${real_limit}_WIDTH__ ${real_limit}$" "$base/macros" >/dev/null +grep "^#define __SIZEOF_COMPLEX${real_limit}__ ${complex_size}$" "$base/macros" >/dev/null + +for name in \ + __VERSION__ __MPU_MACHINE_REGISTER_WIDTH__ __MPU_REAL_IO_LIMIT__ \ + __MPU_MATH_FN_LIMIT__ __INT128_MIN__ __INT1024_MAX__ \ + __REAL128_DIG__ __REAL1024_MAX__ __FLOAT_WORD_ORDER__ \ + __SIZE_TYPE__ __SIZE_WIDTH__ __SIZE_MAX__ __SIZEOF_SIZE_T__ \ + __MCPU_SIZEOF_SSIZE_T__ __SIZEOF_CHAR8_T__ __SIZEOF_CHAR16_T__ \ + __SIZEOF_PTRDIFF_T__; do + if grep "^#define $name\>" "$base/macros" >/dev/null; then exit 1; fi +done + +if grep '^#define __FILE__' "$base/macros" >/dev/null; then exit 1; fi +if grep '^#define __LINE__' "$base/macros" >/dev/null; then exit 1; fi +if grep '^#define __DATE__' "$base/macros" >/dev/null; then exit 1; fi +if grep '^#define __TIME__' "$base/macros" >/dev/null; then exit 1; fi +if grep '^#define __BASE_FILE__' "$base/macros" >/dev/null; then exit 1; fi +if grep '^#define __INCLUDE_LEVEL__' "$base/macros" >/dev/null; then exit 1; fi diff --git a/tests/t0026-dump-config.sh b/tests/t0026-dump-config.sh new file mode 100755 index 0000000..0778f28 --- /dev/null +++ b/tests/t0026-dump-config.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-dconfig-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/test.conf" <<'EOT' +ZETA = /zeta; +ROOT = /opt/mcpu; +ALPHA = $ROOT/include; +MCPU_CPP_INCLUDE_PATH = $ROOT/include/mcpu; +EOT + +"$MCPU_CPP" --config-file "$base/test.conf" -dconfig > "$base/config" + +test -s "$base/config" +LC_ALL=C sort -c "$base/config" +grep '^ALPHA = /opt/mcpu/include;$' "$base/config" >/dev/null +grep '^MCPU_CPP_INCLUDE_PATH = /opt/mcpu/include/mcpu;$' "$base/config" >/dev/null +grep '^ROOT = /opt/mcpu;$' "$base/config" >/dev/null +grep '^ZETA = /zeta;$' "$base/config" >/dev/null +grep '^MCPU_CPP_SYSTEM_INCLUDE_PATH = ' "$base/config" >/dev/null diff --git a/tests/t0027-system-language-path.sh b/tests/t0027-system-language-path.sh new file mode 100755 index 0000000..0947c07 --- /dev/null +++ b/tests/t0027-system-language-path.sh @@ -0,0 +1,44 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-system-lang-$$" +mkdir -p "$base/system/diff" "$base/system/as" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/test.conf" <<EOT +MCPU_CPP_SYSTEM_INCLUDE_PATH = $base/system; +EOT + +cat > "$base/system/common.h" <<'EOT' +common_system_header +EOT +cat > "$base/system/diff/lang.h" <<'EOT' +diff_system_header +EOT +cat > "$base/system/as/lang.h" <<'EOT' +as_system_header +EOT +cat > "$base/system/diff/explicit.h" <<'EOT' +explicit_diff_system_header +EOT + +cat > "$base/main.c" <<'EOT' +#include <common.h> +#lang "diff" +#include <lang.h> +#endlang +#lang "as" +#include <lang.h> +#include <diff/explicit.h> +#endlang +EOT + +"$MCPU_CPP" --config-file "$base/test.conf" "$base/main.c" -o "$base/out" + +grep '^common_system_header$' "$base/out" >/dev/null +grep '^diff_system_header$' "$base/out" >/dev/null +grep '^as_system_header$' "$base/out" >/dev/null +grep '^explicit_diff_system_header$' "$base/out" >/dev/null + +if "$MCPU_CPP" --config-file "$base/test.conf" -nostdinc "$base/main.c" -o "$base/no" 2>/dev/null; then + exit 1 +fi diff --git a/tests/t0028-predefined-ranges.sh b/tests/t0028-predefined-ranges.sh new file mode 100755 index 0000000..72693a2 --- /dev/null +++ b/tests/t0028-predefined-ranges.sh @@ -0,0 +1,95 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-predef-ranges-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +"$MCPU_CPP" --no-config -dMP < /dev/null > "$base/macros" + +# Integer families: derive the largest advertised width, then verify every +# power-of-two family from int8 through that LibMPU limit. +int_limit=`sed -n 's/^#define __INT\([0-9][0-9]*\)_TYPE__ int[0-9][0-9]*$/\1/p' "$base/macros" | sort -n | tail -1` +test -n "$int_limit" +grep "^#define __MCPU_INT_MAX_WIDTH__ ${int_limit}$" "$base/macros" >/dev/null + +bits=8 +while test "$bits" -le "$int_limit"; do + bytes=`expr "$bits" / 8` + grep "^#define __INT${bits}_TYPE__ int${bits}$" "$base/macros" >/dev/null + grep "^#define __UINT${bits}_TYPE__ uint${bits}$" "$base/macros" >/dev/null + grep "^#define __INT${bits}_WIDTH__ ${bits}$" "$base/macros" >/dev/null + grep "^#define __UINT${bits}_WIDTH__ ${bits}$" "$base/macros" >/dev/null + grep "^#define __SIZEOF_INT${bits}__ ${bytes}$" "$base/macros" >/dev/null + grep "^#define __SIZEOF_UINT${bits}__ ${bytes}$" "$base/macros" >/dev/null + + grep "^#define __INT${bits}_DECIMAL_DIG__ [1-9][0-9]*$" "$base/macros" >/dev/null + grep "^#define __UINT${bits}_DECIMAL_DIG__ [1-9][0-9]*$" "$base/macros" >/dev/null + + if test "$bits" -le 256; then + grep "^#define __INT${bits}_MAX__ 0x[0-9a-f][0-9a-f]*$" "$base/macros" >/dev/null + grep "^#define __UINT${bits}_MAX__ 0x[0-9a-f][0-9a-f]*$" "$base/macros" >/dev/null + else + for name in "__INT${bits}_MAX__" "__UINT${bits}_MAX__"; do + if grep "^#define ${name}\\>" "$base/macros" >/dev/null; then exit 1; fi + done + fi + + if grep "^#define __INT${bits}_MIN__\\>" "$base/macros" >/dev/null; then exit 1; fi + bits=`expr "$bits" \* 2` +done + +# Real/Complex families: their complete structural metadata extends through +# MPU_REAL_IO_LIMIT. Complex WIDTH is the language parameter, not storage bits. +real_limit=`sed -n 's/^#define __MCPU_REAL_IO_LIMIT__ \([0-9][0-9]*\)$/\1/p' "$base/macros"` +test -n "$real_limit" +grep "^#define __MCPU_REAL_MAX_WIDTH__ ${real_limit}$" "$base/macros" >/dev/null +grep "^#define __MCPU_COMPLEX_MAX_WIDTH__ ${real_limit}$" "$base/macros" >/dev/null + +bits=32 +while test "$bits" -le "$real_limit"; do + real_bytes=`expr "$bits" / 8` + complex_bytes=`expr "$bits" / 4` + grep "^#define __REAL${bits}_TYPE__ real${bits}$" "$base/macros" >/dev/null + grep "^#define __REAL${bits}_WIDTH__ ${bits}$" "$base/macros" >/dev/null + grep "^#define __SIZEOF_REAL${bits}__ ${real_bytes}$" "$base/macros" >/dev/null + case "$bits" in + 32) exp_bytes=1; max_strlen=20 ;; + 64) exp_bytes=2; max_strlen=40 ;; + 128) exp_bytes=4; max_strlen=60 ;; + 256) exp_bytes=4; max_strlen=80 ;; + 512) exp_bytes=8; max_strlen=160 ;; + 1024) exp_bytes=8; max_strlen=320 ;; + 2048) exp_bytes=16; max_strlen=640 ;; + 4096) exp_bytes=16; max_strlen=1280 ;; + 8192) exp_bytes=32; max_strlen=2560 ;; + 16384) exp_bytes=32; max_strlen=5120 ;; + 32768) exp_bytes=64; max_strlen=10240 ;; + 65536) exp_bytes=64; max_strlen=20480 ;; + *) exit 1 ;; + esac + grep "^#define __SIZEOF_REAL${bits}_EXP__ ${exp_bytes}$" "$base/macros" >/dev/null + grep "^#define __REAL${bits}_MAX_STRLEN__ ${max_strlen}$" "$base/macros" >/dev/null + grep "^#define __COMPLEX${bits}_TYPE__ complex${bits}$" "$base/macros" >/dev/null + grep "^#define __COMPLEX${bits}_WIDTH__ ${bits}$" "$base/macros" >/dev/null + grep "^#define __SIZEOF_COMPLEX${bits}__ ${complex_bytes}$" "$base/macros" >/dev/null + + grep "^#define __REAL${bits}_DECIMAL_DIG__ [1-9][0-9]*$" "$base/macros" >/dev/null + grep "^#define __REAL${bits}_MANT_DIG__ [1-9][0-9]*$" "$base/macros" >/dev/null + + if test "$bits" -le 256; then + grep "^#define __REAL${bits}_MAX__ " "$base/macros" >/dev/null + grep "^#define __REAL${bits}_MIN__ " "$base/macros" >/dev/null + grep "^#define __REAL${bits}_EPSILON__ " "$base/macros" >/dev/null + grep "^#define __REAL${bits}_MAX_EXP__ " "$base/macros" >/dev/null + grep "^#define __REAL${bits}_MIN_EXP__ " "$base/macros" >/dev/null + grep "^#define __REAL${bits}_MAX_10_EXP__ " "$base/macros" >/dev/null + grep "^#define __REAL${bits}_MIN_10_EXP__ " "$base/macros" >/dev/null + else + for suffix in MAX MIN EPSILON MAX_EXP MIN_EXP MAX_10_EXP MIN_10_EXP; do + if grep "^#define __REAL${bits}_${suffix}__\\>" "$base/macros" >/dev/null; then exit 1; fi + done + fi + + if grep "^#define __REAL${bits}_DIG__\\>" "$base/macros" >/dev/null; then exit 1; fi + bits=`expr "$bits" \* 2` +done diff --git a/tests/t0029-predefined-abi-names.sh b/tests/t0029-predefined-abi-names.sh new file mode 100755 index 0000000..37b2f55 --- /dev/null +++ b/tests/t0029-predefined-abi-names.sh @@ -0,0 +1,38 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-predef-names-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +"$MCPU_CPP" --no-config -dMP < /dev/null > "$base/macros" + +grep '^#define __MCPU_SIZE_TYPE__ uint[0-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SIZE_WIDTH__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SIZEOF_SIZE__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SIZE_MAX__ 0x[0-9a-f][0-9a-f]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SSIZE_TYPE__ int[0-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SSIZE_WIDTH__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SIZEOF_SSIZE__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_SSIZE_MAX__ 0x[0-9a-f][0-9a-f]*$' "$base/macros" >/dev/null +grep '^#define __SIZEOF_PTRDIFF__ 8$' "$base/macros" >/dev/null +grep '^#define __BYTE_ORDER__ __MCPU_BYTE_ORDER__$' "$base/macros" >/dev/null +grep '^#define __MCPU_BYTE_ORDER__ __ORDER_\(LITTLE\|BIG\)_ENDIAN__$' "$base/macros" >/dev/null +grep '^#define __MCPU_WORD_ORDER__ __ORDER_\(LITTLE\|BIG\)_ENDIAN__$' "$base/macros" >/dev/null +grep '^#define __MCPU_INT_MAX_WIDTH__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_REAL_MAX_WIDTH__ [1-9][0-9]*$' "$base/macros" >/dev/null +grep '^#define __MCPU_COMPLEX_MAX_WIDTH__ [1-9][0-9]*$' "$base/macros" >/dev/null + +grep '^#define __SIZEOF_CHAR8__ 1$' "$base/macros" >/dev/null +grep '^#define __SIZEOF_CHAR16__ 2$' "$base/macros" >/dev/null + +real_limit=`sed -n 's/^#define __MCPU_REAL_IO_LIMIT__ \([0-9][0-9]*\)$/\1/p' "$base/macros"` +test -n "$real_limit" +grep "^#define __SIZEOF_REAL${real_limit}_EXP__ [1-9][0-9]*$" "$base/macros" >/dev/null +grep "^#define __REAL${real_limit}_MAX_STRLEN__ [1-9][0-9]*$" "$base/macros" >/dev/null + +for name in \ + __SIZE_TYPE__ __SIZE_WIDTH__ __SIZE_MAX__ __SIZEOF_SIZE_T__ \ + __MCPU_SIZEOF_SSIZE_T__ __SIZEOF_CHAR8_T__ __SIZEOF_CHAR16_T__ \ + __SIZEOF_PTRDIFF_T__ __FLOAT_WORD_ORDER__; do + if grep "^#define ${name}\\>" "$base/macros" >/dev/null; then exit 1; fi +done diff --git a/tests/t0030-integer-decimal-digits.sh b/tests/t0030-integer-decimal-digits.sh new file mode 100755 index 0000000..08f7fe2 --- /dev/null +++ b/tests/t0030-integer-decimal-digits.sh @@ -0,0 +1,33 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-int-digs-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +"$MCPU_CPP" --no-config -dMP < /dev/null > "$base/macros" + +check_digs() +{ + bits="$1" + int_digs="$2" + uint_digs="$3" + + grep "^#define __INT${bits}_DECIMAL_DIG__ ${int_digs}$" "$base/macros" >/dev/null + grep "^#define __UINT${bits}_DECIMAL_DIG__ ${uint_digs}$" "$base/macros" >/dev/null +} + +# Decimal digits of the numeric maxima only: no sign and no terminating NUL. +check_digs 8 3 3 +check_digs 16 5 5 +check_digs 32 10 10 +check_digs 64 19 20 +check_digs 128 39 39 +check_digs 256 77 78 +check_digs 512 154 155 +check_digs 1024 308 309 +check_digs 2048 617 617 +check_digs 4096 1233 1234 +check_digs 8192 2466 2467 +check_digs 16384 4932 4933 +check_digs 32768 9864 9865 +check_digs 65536 19729 19729 diff --git a/tests/t0031-stringification.sh b/tests/t0031-stringification.sh new file mode 100755 index 0000000..213d787 --- /dev/null +++ b/tests/t0031-stringification.sh @@ -0,0 +1,50 @@ +#!/bin/sh +set -eu +src="${TMPDIR-/tmp}/mcpu-cpp-stringify-$$.c" +out="${TMPDIR-/tmp}/mcpu-cpp-stringify-$$.out" +dump="${TMPDIR-/tmp}/mcpu-cpp-stringify-$$.dump" +trap 'rm -f "$src" "$out" "$dump"' EXIT HUP INT TERM + +cat > "$src" <<'EOT' +#define A 7 +#define STR(X) #X +#define WSTR(X) # X +#define XSTR(X) STR(X) +#define MIX(X) #X | X +#define BAD(X, Y) X + Y +#define HASH(X) "#" X +#define EMPTY(X) <X> +STR(A) +WSTR(alpha) +XSTR(A) +STR( a + b ) +STR(Привет мир) +STR("a\\b") +STR() +STR(BAD(1)) +MIX(A) +HASH(A) +EMPTY() +#lang "diff" +#define DSTR(X) #X +DSTR(y' + z) +#endlang +EOT + +"$MCPU_CPP" --no-config "$src" -o "$out" + +grep -Fx '"A"' "$out" >/dev/null +grep -Fx '"alpha"' "$out" >/dev/null +grep -Fx '"7"' "$out" >/dev/null +grep -Fx '"a + b"' "$out" >/dev/null +grep -Fx '"Привет мир"' "$out" >/dev/null +grep -Fx '"\"a\\\\b\""' "$out" >/dev/null +grep -Fx '""' "$out" >/dev/null +grep -Fx '"BAD(1)"' "$out" >/dev/null +grep -Fx '"A" | 7' "$out" >/dev/null +grep -Fx '"#" 7' "$out" >/dev/null +grep -Fx '<>' "$out" >/dev/null +grep -Fx '"y'"'"' + z"' "$out" >/dev/null + +"$MCPU_CPP" --no-config -dM "$src" > "$dump" +grep -Fx '#define STR(X) #X' "$dump" >/dev/null diff --git a/tests/t0032-command-line.sh b/tests/t0032-command-line.sh new file mode 100755 index 0000000..e0deb91 --- /dev/null +++ b/tests/t0032-command-line.sh @@ -0,0 +1,119 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-options-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +check() +{ + name=$1 + shift + "$MCPP_OPTIONS_TEST" "$@" > "$base/$name" +} + +check pipe +cat > "$base/pipe.expected" <<'EOT' +input=<stdin> +output=<stdout> +verbose=0 +nostdinc=0 +dump_config=0 +dump_search_dirs=0 +dump_macros=0 +actions=0 +EOT +cmp "$base/pipe.expected" "$base/pipe" + +check files input.S output.s +sed -n '1,2p' "$base/files" > "$base/files.io" +printf '%s\n' 'input=input.S' 'output=output.s' > "$base/files.expected" +cmp "$base/files.expected" "$base/files.io" + +check dash - - +sed -n '1,2p' "$base/dash" > "$base/dash.io" +printf '%s\n' 'input=<stdin>' 'output=<stdout>' > "$base/dash.expected" +cmp "$base/dash.expected" "$base/dash.io" + +check outfile input.S -o result.s +sed -n '1,2p' "$base/outfile" > "$base/outfile.io" +printf '%s\n' 'input=input.S' 'output=result.s' > "$base/outfile.expected" +cmp "$base/outfile.expected" "$base/outfile.io" + +check options -v -nostdinc -Iinc1 -I inc2 -isystem sys -idirafter after input.S - +grep '^verbose=1$' "$base/options" >/dev/null +grep '^nostdinc=1$' "$base/options" >/dev/null +grep '^actions=4$' "$base/options" >/dev/null +grep '^action\[0\]=include-user:inc1$' "$base/options" >/dev/null +grep '^action\[1\]=include-user:inc2$' "$base/options" >/dev/null +grep '^action\[2\]=include-system:sys$' "$base/options" >/dev/null +grep '^action\[3\]=include-after:after$' "$base/options" >/dev/null + +check ordered -DA=1 -UA -DA=2 +sed -n '/^actions=/,$p' "$base/ordered" > "$base/ordered.actions" +cat > "$base/ordered.expected" <<'EOT' +actions=3 +action[0]=define:A=1 +action[1]=undef:A +action[2]=define:A=2 +EOT +cmp "$base/ordered.expected" "$base/ordered.actions" + +check searchdirs -dsearch-dirs +grep '^dump_search_dirs=1$' "$base/searchdirs" >/dev/null + +check dump -dMP input.S -o macros.txt +grep '^dump_macros=1$' "$base/dump" >/dev/null + +check retained -include force.h -imacros macros.h -DA=1 -UOLD -M -MG -w -Wall -Werror input.S output.s +grep '^input=input.S$' "$base/retained" >/dev/null +grep '^output=output.s$' "$base/retained" >/dev/null +grep '^action\[[0-9][0-9]*\]=include:force.h$' "$base/retained" >/dev/null +grep '^action\[[0-9][0-9]*\]=imacros:macros.h$' "$base/retained" >/dev/null +grep '^action\[[0-9][0-9]*\]=define:A=1$' "$base/retained" >/dev/null +grep '^action\[[0-9][0-9]*\]=undef:OLD$' "$base/retained" >/dev/null + +check dumpD -dD input.S +grep '^dump_macros=2$' "$base/dumpD" >/dev/null + +check md -MD input.S +grep '^input=input.S$' "$base/md" >/dev/null +check mmd -MMD input.S +grep '^input=input.S$' "$base/mmd" >/dev/null +check mf -MD -MF deps.mk input.S +grep '^input=input.S$' "$base/mf" >/dev/null +check mfattached -MMD -MFdeps.mk input.S +grep '^input=input.S$' "$base/mfattached" >/dev/null +check mt -M -MT target.o input.S +grep '^action\[[0-9][0-9]*\]=dep-target:target.o$' "$base/mt" >/dev/null +check mtattached -MM -MTtarget.o input.S +grep '^action\[[0-9][0-9]*\]=dep-target:target.o$' "$base/mtattached" >/dev/null +check mq -MD -MQ '$(OBJDIR)/target.o' input.S +grep '^action\[[0-9][0-9]*\]=dep-target-quoted:$(OBJDIR)/target.o$' "$base/mq" >/dev/null +check mqattached -MMD '-MQ$(OBJDIR)/target.o' input.S +grep '^action\[[0-9][0-9]*\]=dep-target-quoted:$(OBJDIR)/target.o$' "$base/mqattached" >/dev/null +if "$MCPP_OPTIONS_TEST" a b c > /dev/null 2>&1; then exit 1; fi +if "$MCPP_OPTIONS_TEST" -o > /dev/null 2>&1; then exit 1; fi +if "$MCPP_OPTIONS_TEST" -isystem > /dev/null 2>&1; then exit 1; fi +if "$MCPP_OPTIONS_TEST" --bad-option > /dev/null 2>&1; then exit 1; fi +if "$MCPP_OPTIONS_TEST" -MF deps.mk input.S > /dev/null 2>&1; then exit 1; fi +if "$MCPP_OPTIONS_TEST" -MT target.o input.S > /dev/null 2>&1; then exit 1; fi +if "$MCPP_OPTIONS_TEST" -MQ target.o input.S > /dev/null 2>&1; then exit 1; fi +if "$MCPP_OPTIONS_TEST" -M -MT > /dev/null 2>&1; then exit 1; fi +if "$MCPP_OPTIONS_TEST" -M -MQ > /dev/null 2>&1; then exit 1; fi + +# Unsupported legacy assertion options remain rejected. +if "$MCPP_OPTIONS_TEST" -Aquestion input.S > /dev/null 2>&1; then exit 1; fi +if "$MCPP_OPTIONS_TEST" -dMA input.S > /dev/null 2>&1; then exit 1; fi + +# Removed compatibility options are not recognized by mcpu-cpp. +for opt in \ + -x -X -out-unix-mode -I- -H -C -P '-$' -r -R -undef -u -dN -g3 -dark \ + -Wimport -Wno-import -pedantic-ANSI-C -pedantic-ANSI-C-errors \ + -lang-k -lang-k++ -lang-k-k++-comments -lang-asm -+ \ + -iprefix -iwithprefix -iwithprefixbefore +do + if "$MCPP_OPTIONS_TEST" "$opt" input.S > /dev/null 2>&1; then + echo "removed option unexpectedly accepted: $opt" >&2 + exit 1 + fi +done diff --git a/tests/t0033-dump-definitions.sh b/tests/t0033-dump-definitions.sh new file mode 100755 index 0000000..2637e53 --- /dev/null +++ b/tests/t0033-dump-definitions.sh @@ -0,0 +1,51 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-dD-$$" +mkdir -p "$base/inc" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/inc/defs.h" <<'EOT' +#ifndef MCPP_TEST_DEFS_H +#define MCPP_TEST_DEFS_H 1 +#define FROM_HEADER 17 /* header definition */ +#endif /* MCPP_TEST_DEFS_H */ +EOT + +cat > "$base/main.c" <<'EOT' +#include <defs.h> +#define LOCAL_VALUE 25 // local definition +LOCAL_VALUE + FROM_HEADER +EOT + +"$MCPU_CPP" --no-config -I"$base/inc" -dD "$base/main.c" -o "$base/out" + +# -dD starts with an input marker, emits predefined definitions with +# <built-in> markers, keeps source #define directives, and still emits the +# ordinary preprocessed result. +grep '^# 0 "'"$base/main.c"'"$' "$base/out" >/dev/null +grep '^# 0 "<built-in>"$' "$base/out" >/dev/null +grep '^#define __MCPU_CPP_VERSION__ "1.0.2"$' "$base/out" >/dev/null +grep '^#define FROM_HEADER 17$' "$base/out" >/dev/null +grep '^#define LOCAL_VALUE 25$' "$base/out" >/dev/null +if grep '^[[:blank:]]*#[[:blank:]]*\(if\|ifdef\|ifndef\|elif\|else\|endif\)\>' "$base/out" >/dev/null; then + exit 1 +fi +grep '^25 + 17$' "$base/out" >/dev/null +if grep '[[:blank:]]$' "$base/out" >/dev/null; then + exit 1 +fi + +# Every predefined definition in the initial block has its own built-in marker. +awk ' + /^# 1 / { exit } + /^#define / { + if( previous != "# 0 \"<built-in>\"") exit 1 + } + { previous = $0 } +' "$base/out" + +# Without -dD the same source definitions are removed from normal output. +"$MCPU_CPP" --no-config -I"$base/inc" "$base/main.c" -o "$base/normal" +if grep '^#define LOCAL_VALUE 25$' "$base/normal" >/dev/null; then + exit 1 +fi diff --git a/tests/t0034-conditionals.sh b/tests/t0034-conditionals.sh new file mode 100755 index 0000000..85f3d66 --- /dev/null +++ b/tests/t0034-conditionals.sh @@ -0,0 +1,136 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-conditionals-$$" +mkdir -p "$base/inc" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/inc/guard.h" <<'EOT' +#ifndef MCPP_TEST_GUARD_H +#define MCPP_TEST_GUARD_H 1 +#define HEADER_VALUE 17 +header_line +#endif +EOT + +cat > "$base/main.c" <<'EOT' +#define A 1 +#define B 2 +#define F(x) ((x) + 1) + +#include <guard.h> +#include <guard.h> + +#if A + B * 3 == 7 && defined(A) && !defined(NO_SUCH_MACRO) +if_line +#else +bad_if +#endif + +#if 0 +#define BAD_SKIPPED 1 +bad_elif_0 +#elif F(B) == 3 +elif_line +#else +bad_elif_else +#endif + +#ifdef A +ifdef_line +#else +bad_ifdef +#endif + +#ifndef NO_SUCH_MACRO +ifndef_line +#endif + +#if 0 +# if 1 / 0 +bad_nested +# endif +#else +nested_skip_line +#endif + +#if 0 && (1 / 0) +bad_short_and +#endif + +#if 1 || (1 / 0) +short_or_line +#endif + +#if 1 ? 2 : 1 / 0 +ternary_true_line +#endif + +#if 0 ? 1 / 0 : 3 +ternary_false_line +#endif + +#if UNKNOWN_IDENTIFIER +bad_unknown +#else +unknown_zero_line +#endif +EOT + +"$MCPU_CPP" --no-config -I"$base/inc" -dD "$base/main.c" -o "$base/out" + +for word in \ + header_line if_line elif_line ifdef_line ifndef_line nested_skip_line \ + short_or_line ternary_true_line ternary_false_line unknown_zero_line; do + test "`grep -c "^${word}$" "$base/out"`" -eq 1 +done + +for word in \ + bad_if bad_elif_0 bad_elif_else bad_ifdef bad_nested bad_short_and bad_unknown; do + if grep "^${word}$" "$base/out" >/dev/null; then exit 1; fi +done + +# Conditional-control directives are consumed by the preprocessor and never +# survive in the normal output, including -dD output. +if grep '^[[:blank:]]*#[[:blank:]]*\(if\|ifdef\|ifndef\|elif\|else\|endif\)\>' "$base/out" >/dev/null; then + exit 1 +fi + +# Active definitions are retained by -dD, skipped ones are not. +grep '^#define A 1$' "$base/out" >/dev/null +grep '^#define HEADER_VALUE 17$' "$base/out" >/dev/null +if grep '^#define BAD_' "$base/out" >/dev/null; then exit 1; fi + +cat > "$base/bad-endif.c" <<'EOT' +#endif +EOT +if "$MCPU_CPP" --no-config "$base/bad-endif.c" -o "$base/bad-out" 2>/dev/null; then + exit 1 +fi + +cat > "$base/bad-else.c" <<'EOT' +#if 1 +#else +#else +#endif +EOT +if "$MCPU_CPP" --no-config "$base/bad-else.c" -o "$base/bad-out" 2>/dev/null; then + exit 1 +fi + +cat > "$base/bad-elif.c" <<'EOT' +#if 0 +#else +#elif 1 +#endif +EOT +if "$MCPU_CPP" --no-config "$base/bad-elif.c" -o "$base/bad-out" 2>/dev/null; then + exit 1 +fi + +cat > "$base/unterminated.c" <<'EOT' +#if 1 +unterminated +EOT +if "$MCPU_CPP" --no-config "$base/unterminated.c" -o "$base/bad-out" 2>/dev/null; then + exit 1 +fi diff --git a/tests/t0035-line-control.sh b/tests/t0035-line-control.sh new file mode 100755 index 0000000..b892015 --- /dev/null +++ b/tests/t0035-line-control.sh @@ -0,0 +1,41 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-line-control-$$" +mkdir -p "$base/inc" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/inc/one.h" <<'EOT' +int header_line = __LINE__; +EOT + +cat > "$base/main.c" <<'EOT' +int before = __LINE__; +#line 62 "main.y" +int after = __LINE__; +const char *file_after = __FILE__; +#include "one.h" +int returned = __LINE__; +#define LINE_NUMBER 90 +#line LINE_NUMBER "macro-line.y" +int macro_line = __LINE__; +EOT + +"$MCPU_CPP" --no-config -I"$base/inc" "$base/main.c" -o "$base/out" + +# Generated line information uses GNU linemarker syntax, not input #line syntax. +if grep '^#line[[:blank:]]' "$base/out" >/dev/null; then exit 1; fi + +grep "^# 1 \"$base/main.c\"$" "$base/out" >/dev/null +grep '^# 62 "main.y"$' "$base/out" >/dev/null +grep '^int after = 62;$' "$base/out" >/dev/null +grep '^const char \*file_after = "main.y";$' "$base/out" >/dev/null + +# Flag 1 means entering an included file; flag 2 means returning to its includer. +grep "^# 1 \"$base/inc/one.h\" 1$" "$base/out" >/dev/null +grep '^# 65 "main.y" 2$' "$base/out" >/dev/null +grep '^int returned = 65;$' "$base/out" >/dev/null + +# Arguments of #line are macro-expanded before interpretation. +grep '^# 90 "macro-line.y"$' "$base/out" >/dev/null +grep '^int macro_line = 90;$' "$base/out" >/dev/null + diff --git a/tests/t0036-lang-string.sh b/tests/t0036-lang-string.sh new file mode 100755 index 0000000..8e40022 --- /dev/null +++ b/tests/t0036-lang-string.sh @@ -0,0 +1,120 @@ +#!/bin/sh +set -eu +dir="${TMPDIR-/tmp}/mcpu-cpp-lang-string-$$" +mkdir -p "$dir" +trap 'rm -rf "$dir"' EXIT HUP INT TERM + +cat > "$dir/normalize.c" <<'EOT' + # lang "DiFf" +#endlang + #lang "dIfT" +#endlang +EOT +"$MCPU_CPP" --no-config "$dir/normalize.c" -o "$dir/normalize.out" +grep '^#lang "DiFf"$' "$dir/normalize.out" >/dev/null +grep '^#lang "dIfT"$' "$dir/normalize.out" >/dev/null +if grep '^[[:space:]][[:space:]]*#lang' "$dir/normalize.out" >/dev/null +then + echo '#lang leading whitespace was not normalized' >&2 + exit 1 +fi + +cat > "$dir/case.c" <<'EOT' +#lang "diff" +#endlang +#lang "Diff" +#endlang +#lang "DIFF" +#endlang +#lang "dIfF" +y' = 1; +#endlang +#lang "dIfT" +#endlang +#lang "ALG" +#endlang +#lang "As" +#endlang +#lang "aVm" +#endlang +#lang "aCs" +#endlang +EOT +"$MCPU_CPP" --no-config "$dir/case.c" -o "$dir/case.out" +for spelling in diff Diff DIFF dIfF dIfT ALG As aVm aCs +do + grep "^#lang \"$spelling\"$" "$dir/case.out" >/dev/null +done +grep "^y' = 1;$" "$dir/case.out" >/dev/null + +check_bad() +{ + name=$1 + pattern=$2 + if "$MCPU_CPP" --no-config "$dir/$name.c" -o "$dir/$name.out" 2> "$dir/$name.err" + then + echo "$name unexpectedly accepted" >&2 + exit 1 + fi + grep -F "$pattern" "$dir/$name.err" >/dev/null +} + +cat > "$dir/unquoted.c" <<'EOT' +#lang diff +EOT +check_bad unquoted 'string constant expected after #lang' + +cat > "$dir/empty.c" <<'EOT' +#lang "" +EOT +check_bad empty 'empty language name in #lang' + +cat > "$dir/leading-space.c" <<'EOT' +#lang " diff" +EOT +check_bad leading-space 'one word without whitespace' + +cat > "$dir/trailing-space.c" <<'EOT' +#lang "diff " +EOT +check_bad trailing-space 'one word without whitespace' + +cat > "$dir/internal-space.c" <<'EOT' +#lang "di ff" +EOT +check_bad internal-space 'one word without whitespace' + +printf '#lang "di\tff"\n' > "$dir/internal-tab.c" +check_bad internal-tab 'one word without whitespace' + +cat > "$dir/unknown.c" <<'EOT' +#lang "cplusplus" +EOT +check_bad unknown "unknown language 'cplusplus'" + +cat > "$dir/zero.c" <<'EOT' +#lang "0" +EOT +check_bad zero "unknown language '0'" + +cat > "$dir/extra.c" <<'EOT' +#lang "diff" extra +EOT +check_bad extra 'extra text after #lang string constant' + +cat > "$dir/multiline.c" <<'EOT' +#lang "di +ff" +EOT +check_bad multiline 'unterminated #lang string constant' + +cat > "$dir/spliced.c" <<'EOT' +#lang "di\ +ff" +EOT +check_bad spliced 'one physical source line' + +cat > "$dir/no-escape.c" <<'EOT' +#lang "d\iff" +EOT +check_bad no-escape "unknown language 'd\\iff'" diff --git a/tests/t0037-token-concatenation.sh b/tests/t0037-token-concatenation.sh new file mode 100755 index 0000000..f2d2c3f --- /dev/null +++ b/tests/t0037-token-concatenation.sh @@ -0,0 +1,122 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-concat-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/main.c" <<'EOT' +#define A left +#define B right +#define AB raw_result +#define leftright expanded_result +#define CAT(X, Y) X ## Y +#define XCAT(X, Y) CAT(X, Y) +#define AFTERX(X) X_ ## X +#define XAFTERX(X) AFTERX(X) +#define TABLESIZE 1024 +#define BUFSIZE TABLESIZE +#define MADE 91 +#define MK(A, B) A ## B +#define L(X) X ## tail +#define R(X) head ## X +#define CHAIN(A, B, C) A ## B ## C +#define OBJECT obj ## ect +#define object object_result +#define COMMAND(NAME) #NAME | NAME ## _command +#define quit_command command_result +#define CALL(NAME) NAME ## _fn(1) +#define run_fn(X) called_result +#define ONE1 1 +#define PCAT(A, B) A ## B +#define RUN(A, B) A ## ## B +#define ab adjacent_result +#define TEXT "a ## b" +#define COMMENT_CAT(A, B) A /* left */ ## /* right */ B +#define DIRECT_EMPTY +#define xDIRECT_EMPTY direct_empty_raw +#define EXPAND_EMPTY(A, B) CAT(A, B) + +CAT(A, B) +XCAT(A, B) +AFTERX(BUFSIZE) +XAFTERX(BUFSIZE) +MK(MA, DE) +CAT(x y, z) +CAT(x, y z) +L() +R() +CHAIN(x, , z) +OBJECT +CAT(1.5, e3) +CAT(+, =) +CAT(L, "wide") +CAT(u8, "utf8") +COMMAND(quit) +CALL(run) +RUN(a, b) +CAT(x, DIRECT_EMPTY) +EXPAND_EMPTY(x, DIRECT_EMPTY) +TEXT +COMMENT_CAT(c, d) +CAT(e /* argument comment */, f) +#if PCAT(ONE, 1) +conditional_result +#endif +EOT + +"$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" + +grep -Fx 'raw_result' "$base/out" >/dev/null +grep -Fx 'expanded_result' "$base/out" >/dev/null +grep -Fx 'X_BUFSIZE' "$base/out" >/dev/null +grep -Fx 'X_1024' "$base/out" >/dev/null +grep -Fx '91' "$base/out" >/dev/null +grep -Fx 'x yz' "$base/out" >/dev/null +grep -Fx 'xy z' "$base/out" >/dev/null +grep -Fx 'tail' "$base/out" >/dev/null +grep -Fx 'head' "$base/out" >/dev/null +grep -Fx 'xz' "$base/out" >/dev/null +grep -Fx 'object_result' "$base/out" >/dev/null +grep -Fx '1.5e3' "$base/out" >/dev/null +grep -Fx '+=' "$base/out" >/dev/null +grep -Fx 'L"wide"' "$base/out" >/dev/null +grep -Fx 'u8"utf8"' "$base/out" >/dev/null +grep -Fx '"quit" | command_result' "$base/out" >/dev/null +grep -Fx 'called_result' "$base/out" >/dev/null +grep -Fx 'adjacent_result' "$base/out" >/dev/null +grep -Fx 'direct_empty_raw' "$base/out" >/dev/null +grep -Fx 'x' "$base/out" >/dev/null +grep -Fx '"a ## b"' "$base/out" >/dev/null +grep -Fx 'cd' "$base/out" >/dev/null +grep -Fx 'ef' "$base/out" >/dev/null +grep -Fx 'conditional_result' "$base/out" >/dev/null + +"$MCPU_CPP" --no-config -dM "$base/main.c" > "$base/dump" +grep -Fx '#define CAT(X,Y) X ## Y' "$base/dump" >/dev/null +grep -Fx '#define COMMAND(NAME) #NAME | NAME ## _command' "$base/dump" >/dev/null + +cat > "$base/invalid.c" <<'EOT' +#define BAD(X) X ## + +BAD(foo) +EOT +"$MCPU_CPP" --no-config "$base/invalid.c" -o "$base/invalid.out" 2>"$base/invalid.err" +grep 'pasting "foo" and "+" does not give a valid preprocessing token' "$base/invalid.err" >/dev/null +grep 'foo' "$base/invalid.out" | tr -d '[:space:]' | grep -Fx 'foo+' >/dev/null + +cat > "$base/first.c" <<'EOT' +#define FIRST(X) ## X +EOT +if "$MCPU_CPP" --no-config "$base/first.c" -o "$base/first.out" 2>"$base/first.err"; then + echo "leading ## accepted" >&2 + exit 1 +fi +grep "'##' cannot appear at the beginning of a macro replacement list" "$base/first.err" >/dev/null + +cat > "$base/last.c" <<'EOT' +#define LAST(X) X ## +EOT +if "$MCPU_CPP" --no-config "$base/last.c" -o "$base/last.out" 2>"$base/last.err"; then + echo "trailing ## accepted" >&2 + exit 1 +fi +grep "'##' cannot appear at the end of a macro replacement list" "$base/last.err" >/dev/null diff --git a/tests/t0038-ucs2-identifiers.sh b/tests/t0038-ucs2-identifiers.sh new file mode 100755 index 0000000..d1ae5db --- /dev/null +++ b/tests/t0038-ucs2-identifiers.sh @@ -0,0 +1,122 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-ucs2-identifiers-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +# The source file is UTF-8 externally. mcpu-cpp decodes it to strict UCS-2, +# and identifier classification is then performed with the locale-independent +# Unicode XID_Start/XID_Continue predicates provided by LibMPUIO. +cat > "$base/main.c" <<'EOT' +#define АНДРЕЙ 11 +#define résumé 12 +#define Élève 13 +#define ΩΜΕΓΑ 14 +#define переменная2 15 +#define a١ 16 +#define é 17 +#define СУММА(лево, право) лево + право +#define СТРОКА(имя) #имя +#define СКЛЕИТЬ(лево, право) лево ## право +#define АНДРЕЙ2 18 +#define ВРЕМЕННЫЙ 19 +#undef ВРЕМЕННЫЙ +#define VALUE$OLD 20 +#define ДОЛЛАР$ИМЯ 21 +#define DOLLAR_PARAM(x$old) x$old +#define CAT_DOLLAR(a,b) a ## b +#define LEFT$RIGHT 22 + +АНДРЕЙ +résumé +Élève +ΩΜΕΓΑ +переменная2 +a١ +é +СУММА(АНДРЕЙ, résumé) +СТРОКА(АНДРЕЙ) +СКЛЕИТЬ(АНДР, ЕЙ) +СКЛЕИТЬ(АНДРЕЙ, 2) +андрей +ВРЕМЕННЫЙ +VALUE$OLD +ДОЛЛАР$ИМЯ +DOLLAR_PARAM(23) +CAT_DOLLAR(LEFT$, RIGHT) + +#ifdef АНДРЕЙ +ifdef_ok +#endif + +#ifndef ВРЕМЕННЫЙ +ifndef_ok +#endif + +#if defined(résumé) && defined(ΩΜΕΓΑ) +defined_ok +#endif + +#ifdef VALUE$OLD +dollar_ifdef_ok +#endif + +#if defined(ДОЛЛАР$ИМЯ) +dollar_defined_ok +#endif +EOT + +"$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" + +grep -Fx '11' "$base/out" >/dev/null +grep -Fx '12' "$base/out" >/dev/null +grep -Fx '13' "$base/out" >/dev/null +grep -Fx '14' "$base/out" >/dev/null +grep -Fx '15' "$base/out" >/dev/null +grep -Fx '16' "$base/out" >/dev/null +grep -Fx '17' "$base/out" >/dev/null +grep -Fx '11 + 12' "$base/out" >/dev/null +grep -Fx '"АНДРЕЙ"' "$base/out" >/dev/null +# Raw operands of ## are concatenated, then the result is rescanned. +grep -Fx '11' "$base/out" >/dev/null +grep -Fx '18' "$base/out" >/dev/null +grep -Fx 'андрей' "$base/out" >/dev/null +grep -Fx 'ВРЕМЕННЫЙ' "$base/out" >/dev/null +grep -Fx 'ifdef_ok' "$base/out" >/dev/null +grep -Fx 'ifndef_ok' "$base/out" >/dev/null +grep -Fx 'defined_ok' "$base/out" >/dev/null +grep -Fx '20' "$base/out" >/dev/null +grep -Fx '21' "$base/out" >/dev/null +grep -Fx '23' "$base/out" >/dev/null +grep -Fx '22' "$base/out" >/dev/null +grep -Fx 'dollar_ifdef_ok' "$base/out" >/dev/null +grep -Fx 'dollar_defined_ok' "$base/out" >/dev/null + +# XID_Continue admits combining marks after an identifier start. The same +# combining mark is not XID_Start and therefore cannot begin a macro name. +printf '#define \314\201bad 1\n' > "$base/bad-start.c" +if "$MCPU_CPP" --no-config "$base/bad-start.c" -o "$base/bad-start.out" 2>"$base/bad-start.err"; then + echo "combining mark accepted as identifier start" >&2 + exit 1 +fi + +grep 'macro name expected after #define' "$base/bad-start.err" >/dev/null + +# A decimal digit is XID_Continue but not XID_Start. Non-ASCII digits are +# therefore legal after the first character but cannot start an identifier. +printf '#define \331\241bad 1\n' > "$base/bad-digit-start.c" +if "$MCPU_CPP" --no-config "$base/bad-digit-start.c" -o "$base/bad-digit-start.out" 2>"$base/bad-digit-start.err"; then + echo "non-ASCII digit accepted as identifier start" >&2 + exit 1 +fi + +grep 'macro name expected after #define' "$base/bad-digit-start.err" >/dev/null + + +# Dollar is an identifier-continuation extension, never an identifier start. +printf '#define $BAD 1\n' > "$base/bad-dollar-start.c" +if "$MCPU_CPP" --no-config "$base/bad-dollar-start.c" -o "$base/bad-dollar-start.out" 2>"$base/bad-dollar-start.err"; then + echo "dollar accepted as identifier start" >&2 + exit 1 +fi +grep 'macro name expected after #define' "$base/bad-dollar-start.err" >/dev/null diff --git a/tests/t0039-command-line-macros.sh b/tests/t0039-command-line-macros.sh new file mode 100755 index 0000000..94a5ec3 --- /dev/null +++ b/tests/t0039-command-line-macros.sh @@ -0,0 +1,174 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-command-line-macros-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/main.c" <<'EOT' +OBJECT +DEFAULT +EMPTY marker +СУММА(10, 7) +АНДРЕЙ +résumé +ПОРЯДОК +СТРОКА(АНДРЕЙ) +СКЛЕИТЬ(АНД,РЕЙ) +#ifdef __MCPU_CPP_VERSION__ +BUILTIN_PRESENT +#else +BUILTIN_REMOVED +#endif +#if defined(КОМАНДА) && КОМАНДА == 31 +CONDITIONAL_OK +#else +CONDITIONAL_BAD +#endif +EOT + +"$MCPU_CPP" --no-config \ + -D OBJECT=17 \ + -DDEFAULT \ + -DEMPTY= \ + '-DСУММА(лево,право)=лево + право' \ + -DАНДРЕЙ=23 \ + -Drésumé=29 \ + -DПОРЯДОК=1 -U ПОРЯДОК -DПОРЯДОК=2 \ + '-DСТРОКА(x)=#x' \ + '-DСКЛЕИТЬ(a,b)=a ## b' \ + -DАНДРЕЙ=23 \ + -U__MCPU_CPP_VERSION__ \ + -DКОМАНДА=31 \ + "$base/main.c" -o "$base/out" + +grep '^17$' "$base/out" >/dev/null +grep '^1$' "$base/out" >/dev/null +grep '^ marker$' "$base/out" >/dev/null +grep '^10 + 7$' "$base/out" >/dev/null +test "`grep -c '^23$' "$base/out"`" -eq 2 +grep '^29$' "$base/out" >/dev/null +grep '^2$' "$base/out" >/dev/null +grep '^"АНДРЕЙ"$' "$base/out" >/dev/null +grep '^BUILTIN_REMOVED$' "$base/out" >/dev/null +if grep '^BUILTIN_PRESENT$' "$base/out" >/dev/null; then exit 1; fi +grep '^CONDITIONAL_OK$' "$base/out" >/dev/null +if grep '^CONDITIONAL_BAD$' "$base/out" >/dev/null; then exit 1; fi + +# A function-like -D without '=' has the GNU meaning "replacement is 1". +printf '%s\n' 'F(99)' > "$base/default-func.c" +"$MCPU_CPP" --no-config '-DF(x)' "$base/default-func.c" -o "$base/default-func.out" +grep '^1$' "$base/default-func.out" >/dev/null + +# Unicode XID continuation works on the command line as it does in source. +combining=$(printf 'e\314\201') +printf '%s\n' "$combining" > "$base/combining.c" +"$MCPU_CPP" --no-config "-D$combining=37" "$base/combining.c" -o "$base/combining.out" +grep '^37$' "$base/combining.out" >/dev/null + +arabic_three=$(printf '\331\243') +name="ИМЯ$arabic_three" +printf '%s\n' "$name" > "$base/digit.c" +"$MCPU_CPP" --no-config "-D$name=43" "$base/digit.c" -o "$base/digit.out" +grep '^43$' "$base/digit.out" >/dev/null + +# Command-line definitions pass through the same phase-3 comment cleanup as +# source #define directives. +printf '%s\n' 'COMMENTED' > "$base/comment.c" +"$MCPU_CPP" --no-config '-DCOMMENTED=left/* comment */right' \ + "$base/comment.c" -o "$base/comment.out" +grep '^left right$' "$base/comment.out" >/dev/null + +# -dM observes the final command-line macro state. +printf '%s\n' '' > "$base/empty.c" +"$MCPU_CPP" --no-config -dM -DПОРЯДОК=1 -UПОРЯДОК -DПОРЯДОК=2 \ + "$base/empty.c" -o "$base/macros" +grep '^#define ПОРЯДОК 2$' "$base/macros" >/dev/null + +# '$' is accepted after the first identifier character. Single quotes protect +# it from shell parameter expansion; without quotes a backslash can do the same. +"$MCPU_CPP" --no-config -dD '-DАНДРЕЙ$_Y=62' -DОЛЬГА\$OLD=37 \ + "$base/empty.c" -o "$base/dollar-command-line" +grep '^#define АНДРЕЙ$_Y 62$' "$base/dollar-command-line" >/dev/null +grep '^#define ОЛЬГА$OLD 37$' "$base/dollar-command-line" >/dev/null + +# '$' is not an identifier-start character. +if "$MCPU_CPP" --no-config '-D$BAD=1' "$base/empty.c" -o "$base/bad" \ + >"$base/dollar-start.stdout" 2>"$base/dollar-start.stderr"; then + exit 1 +fi +grep 'macro name expected after #define' "$base/dollar-start.stderr" >/dev/null + +# For -D/-U an invalid tail after a valid identifier is discarded rather than +# becoming part of the replacement list. The value after '=' is preserved. +"$MCPU_CPP" --no-config -dD '-DАНДРЕЙ@XYZ=62' '-DABC+TAIL=17' \ + '-UREMOVE@TAIL' -DREMOVE=99 "$base/empty.c" -o "$base/invalid-tail" +grep '^#define АНДРЕЙ 62$' "$base/invalid-tail" >/dev/null +grep '^#define ABC 17$' "$base/invalid-tail" >/dev/null +grep '^#define REMOVE 99$' "$base/invalid-tail" >/dev/null +if grep '^#define АНДРЕЙ @XYZ 62$' "$base/invalid-tail" >/dev/null; then + exit 1 +fi + +# Explicit '=' in an -U tail is just another invalid suffix and is discarded. +"$MCPU_CPP" --no-config -DNAME=1 '-UNAME=bad' -dM "$base/empty.c" -o "$base/undef-tail" +if grep '^#define NAME ' "$base/undef-tail" >/dev/null; then + exit 1 +fi + +# In -dD output command-line definitions have their own GNU-style origin +# marker and must not be reported as predefined/builtin macros. +"$MCPU_CPP" --no-config -dD -DАНДРЕЙ_К=62 -DОЛЬГА=37 \ + "$base/empty.c" -o "$base/dD-command-line" +awk ' + /^#define АНДРЕЙ_К 62$/ { + if( previous != "# 0 \"<command-line>\"") exit 1 + andrey = 1 + } + /^#define ОЛЬГА 37$/ { + if( previous != "# 0 \"<command-line>\"") exit 1 + olga = 1 + } + /^#define __MCPU_CPP_VERSION__ / { + if( previous != "# 0 \"<built-in>\"") exit 1 + builtin = 1 + } + { previous = $0 } + END { if( !andrey || !olga || !builtin ) exit 1 } +' "$base/dD-command-line" + +# An unterminated block comment in a command-line definition is diagnosed. +if "$MCPU_CPP" --no-config '-DBROKEN=left/*' "$base/empty.c" -o "$base/bad" \ + >"$base/comment-bad.stdout" 2>"$base/comment-bad.stderr"; then + exit 1 +fi +grep -- '-D: unterminated comment' "$base/comment-bad.stderr" >/dev/null + +# Malformed macro names are rejected by the ordinary #define/#undef parsers. +if "$MCPU_CPP" --no-config -D1ABC=2 "$base/empty.c" -o "$base/bad" \ + >"$base/bad.stdout" 2>"$base/bad.stderr"; then + exit 1 +fi +grep 'macro name expected after #define' "$base/bad.stderr" >/dev/null + + +# Only -D/-U payloads are decoded. Invalid UTF-8 and valid supplementary-plane +# UTF-8 are rejected because the internal text model is strict UCS-2. +bad_utf8=$(printf '\377') +if "$MCPU_CPP" --no-config "-D${bad_utf8}=1" "$base/empty.c" -o "$base/bad" \ + >"$base/utf8.stdout" 2>"$base/utf8.stderr"; then + exit 1 +fi +grep -- '-D: invalid UTF-8 or non-UCS-2 character' "$base/utf8.stderr" >/dev/null + +non_ucs2=$(printf '\360\237\230\200') +if "$MCPU_CPP" --no-config "-D${non_ucs2}=1" "$base/empty.c" -o "$base/bad" \ + >"$base/nonucs2.stdout" 2>"$base/nonucs2.stderr"; then + exit 1 +fi +grep -- '-D: invalid UTF-8 or non-UCS-2 character' "$base/nonucs2.stderr" >/dev/null + +if "$MCPU_CPP" --no-config "-U${bad_utf8}" "$base/empty.c" -o "$base/bad" \ + >"$base/utf8u.stdout" 2>"$base/utf8u.stderr"; then + exit 1 +fi +grep -- '-U: invalid UTF-8 or non-UCS-2 character' "$base/utf8u.stderr" >/dev/null diff --git a/tests/t0040-zubr-expression.sh b/tests/t0040-zubr-expression.sh new file mode 100755 index 0000000..d95c629 --- /dev/null +++ b/tests/t0040-zubr-expression.sh @@ -0,0 +1,79 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-zubr-expression-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/main.c" <<'EOT' +#if 0b1010 == 10 && 077 == 63 && 0x2a == 42 +radix_ok +#else +bad_radix +#endif + +#if 9223372036854775807 == 0x7fffffffffffffff +int64_ok +#else +bad_int64 +#endif + +#if 0xffffffffffffffffU > 9223372036854775807 +uint64_ok +#else +bad_uint64 +#endif + +#if 'A' == 65 && 'Я' == 0x42f && '\x42f' == 0x42f && '\101' == 65 +ucs2_char_ok +#else +bad_ucs2_char +#endif + +#if -1 < 0 && (8 << -1) == 4 && (4 >> -1) == 8 +signed_shift_ok +#else +bad_signed_shift +#endif + +#if 0 && (1 / 0) +bad_short_and +#endif + +#if 1 || (1 / 0) +short_or_ok +#endif + +#if 1 ? 7 : 1 / 0 +ternary_ok +#endif +EOT + +"$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" + +for word in \ + radix_ok int64_ok uint64_ok ucs2_char_ok signed_shift_ok short_or_ok ternary_ok; do + test "`grep -c "^${word}$" "$base/out"`" -eq 1 +done + +for word in \ + bad_radix bad_int64 bad_uint64 bad_ucs2_char bad_signed_shift bad_short_and; do + if grep "^${word}$" "$base/out" >/dev/null; then exit 1; fi +done + +cat > "$base/bad-float.c" <<'EOT' +#if 1.0 +bad +#endif +EOT +if "$MCPU_CPP" --no-config "$base/bad-float.c" -o "$base/bad-out" 2>/dev/null; then + exit 1 +fi + +cat > "$base/bad-inc.c" <<'EOT' +#if 1++2 +bad +#endif +EOT +if "$MCPU_CPP" --no-config "$base/bad-inc.c" -o "$base/bad-out" 2>/dev/null; then + exit 1 +fi diff --git a/tests/t0041-integer-width-suffix.sh b/tests/t0041-integer-width-suffix.sh new file mode 100755 index 0000000..a59483f --- /dev/null +++ b/tests/t0041-integer-width-suffix.sh @@ -0,0 +1,95 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-integer-width-suffix-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/main.c" <<'EOT' +#if 0x7fz8 == 127 +z8_positive +#endif +#if 0x80z8 == -128 +z8_sign_extend +#endif +#if 0xffz8 == -1 +z8_minus_one +#endif +#if 0xffz8u == 255 +z8_zero_extend +#endif +#if 0x1ffz8 == -1 && 0x1ffz8U == 255 +z8_truncate +#endif +#if 0x8000z16 == -32768 && 0xffffz16U == 65535 +z16_ok +#endif +#if 0x80000000z32 < 0 && 0xffffffffz32u == 4294967295U +z32_ok +#endif +#if 0xffffffffffffffffz64 == -1 +z64_signed +#endif +#if 0xffffffffffffffffZ064U > 0 +z64_unsigned_leading_zero_width +#endif +#if 0x7fz8 + 1 == 128 +operations_are_64_bit +#endif +#if (~0xffz8u) == 0xffffffffffffff00U +compl_is_64_bit +#endif +#if (0x80z8 >> 1) == -64 && (0x80z8u >> 1) == 64 +shift_uses_promoted_value +#endif +#if 1z24 == 1 +invalid_width_ignored +#endif +#if 1z024U == 1U +invalid_width_u_preserved +#endif +#if 1U == 1 && 1u == 1 +plain_u_ok +#endif +EOT + +"$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" 2>"$base/err" + +for word in \ + z8_positive z8_sign_extend z8_minus_one z8_zero_extend z8_truncate \ + z16_ok z32_ok z64_signed z64_unsigned_leading_zero_width \ + operations_are_64_bit compl_is_64_bit shift_uses_promoted_value \ + invalid_width_ignored invalid_width_u_preserved plain_u_ok; do + test "`grep -c "^${word}$" "$base/out"`" -eq 1 +done + +test "`grep -c 'warning: invalid zNNN integer-width suffix; suffix ignored' "$base/err"`" -eq 2 + +for bad in \ + '1z128' \ + '1z256u' \ + '1z32undefined' \ + '1z32ufoo' \ + '1z32$foo' \ + '1L' \ + '1LL' \ + '18446744073709551616'; do + cat > "$base/bad.c" <<EOT +#if $bad +bad +#endif +EOT + if "$MCPU_CPP" --no-config "$base/bad.c" -o "$base/bad-out" 2>"$base/bad-err"; then + echo "accepted invalid integer constant: $bad" >&2 + exit 1 + fi +done + +cat > "$base/wide.c" <<'EOT' +#if 1z128 +bad +#endif +EOT +if "$MCPU_CPP" --no-config "$base/wide.c" -o "$base/bad-out" 2>"$base/wide.err"; then + exit 1 +fi +grep 'error: integer constants wider than 64 bits are not allowed in conditional directives' "$base/wide.err" >/dev/null diff --git a/tests/t0042-diagnostics.sh b/tests/t0042-diagnostics.sh new file mode 100755 index 0000000..9510332 --- /dev/null +++ b/tests/t0042-diagnostics.sh @@ -0,0 +1,68 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-diagnostics-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/warning.c" <<'EOT' +#define MESSAGE expanded_message +before +#warning MESSAGE is not expanded +#warning one /* removed comment */ two +#warning "spaces inside string" +#warning Привет мир +#if 0 +#warning hidden warning +#error hidden error +#endif +after +EOT + +"$MCPU_CPP" --no-config "$base/warning.c" -o "$base/warning.out" 2>"$base/warning.err" + +grep '^before$' "$base/warning.out" >/dev/null +grep '^after$' "$base/warning.out" >/dev/null +! grep '#warning' "$base/warning.out" >/dev/null + +grep 'warning: #warning MESSAGE is not expanded$' "$base/warning.err" >/dev/null +grep 'warning: #warning one two$' "$base/warning.err" >/dev/null +grep 'warning: #warning "spaces inside string"$' "$base/warning.err" >/dev/null +grep 'warning: #warning Привет мир$' "$base/warning.err" >/dev/null +! grep 'expanded_message' "$base/warning.err" >/dev/null +! grep 'hidden warning' "$base/warning.err" >/dev/null +! grep 'hidden error' "$base/warning.err" >/dev/null + +"$MCPU_CPP" --no-config -dD "$base/warning.c" -o "$base/warning-dd.out" 2>"$base/warning-dd.err" +grep '^#define MESSAGE expanded_message$' "$base/warning-dd.out" >/dev/null +! grep '#warning' "$base/warning-dd.out" >/dev/null +grep 'warning: #warning MESSAGE is not expanded$' "$base/warning-dd.err" >/dev/null + +cat > "$base/line.c" <<'EOT' +#line 62 "generated.mc" +#warning logical location +ok +EOT +"$MCPU_CPP" --no-config "$base/line.c" -o "$base/line.out" 2>"$base/line.err" +grep '^generated\.mc:62: warning: #warning logical location$' "$base/line.err" >/dev/null +grep '^ok$' "$base/line.out" >/dev/null + +cat > "$base/error.c" <<'EOT' +#define WHY replacement +#error WHY failed +never +EOT +if "$MCPU_CPP" --no-config "$base/error.c" -o "$base/error.out" 2>"$base/error.err"; then + echo '#error did not stop preprocessing' >&2 + exit 1 +fi +grep 'error: #error WHY failed$' "$base/error.err" >/dev/null +! grep 'replacement' "$base/error.err" >/dev/null + + +cat > "$base/empty.c" <<'EOT' +#warning +ok +EOT +"$MCPU_CPP" --no-config "$base/empty.c" -o "$base/empty.out" 2>"$base/empty.err" +grep 'warning: #warning$' "$base/empty.err" >/dev/null +grep '^ok$' "$base/empty.out" >/dev/null diff --git a/tests/t0043-include-next.sh b/tests/t0043-include-next.sh new file mode 100755 index 0000000..a2d86c3 --- /dev/null +++ b/tests/t0043-include-next.sh @@ -0,0 +1,87 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-include-next-$$" +mkdir -p "$base/wrapper" "$base/system" "$base/after" "$base/local" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/config" <<EOT +MCPU_CPP_SYSTEM_INCLUDE_PATH = $base/system; +EOT + +cat > "$base/main.c" <<'EOT' +#include <wrapped.h> +#include "local/local.h" +#if 0 +#include_next <never.h> +#endif +EOT + +cat > "$base/wrapper/wrapped.h" <<'EOT' +wrapper_begin +#line 500 "virtual/wrapped.h" +#define NEXT_WRAPPED <wrapped.h> +#include_next NEXT_WRAPPED +wrapper_end +EOT + +cat > "$base/system/wrapped.h" <<'EOT' +system_begin +#include_next "wrapped.h" +system_end +EOT + +cat > "$base/after/wrapped.h" <<'EOT' +after_header +EOT + +cat > "$base/local/local.h" <<'EOT' +local_header +#include_next <from_chain.h> +EOT + +cat > "$base/wrapper/from_chain.h" <<'EOT' +from_chain_wrapper +EOT + +"$MCPU_CPP" --config-file "$base/config" \ + -isystem "$base/wrapper" -idirafter "$base/after" \ + "$base/main.c" -o "$base/out" + +# The wrapper header found through -isystem continues into the configured +# system include root, then that header continues into -idirafter. The quote +# form of #include_next must not re-search the current source directory. +awk ' + /wrapper_begin/ { a = NR } + /system_begin/ { b = NR } + /after_header/ { c = NR } + /system_end/ { d = NR } + /wrapper_end/ { e = NR } + END { exit !(a && a < b && b < c && c < d && d < e) } +' "$base/out" + +# A header found by ordinary quoted-source-directory lookup has no configured +# search entry to skip, so include_next begins with the configured chain. +grep '^local_header$' "$base/out" >/dev/null +grep '^from_chain_wrapper$' "$base/out" >/dev/null + +# Macro-expanded include_next operands use the same include operand parser. +grep '^system_begin$' "$base/out" >/dev/null + +# Inactive conditional branches must not execute include_next. +! grep 'never.h' "$base/out" >/dev/null + +# If no later search entry contains the file, include_next is an error rather +# than falling back to the directory that supplied the current header. +cat > "$base/wrapper/last.h" <<'EOT' +#include_next <last.h> +EOT +cat > "$base/last-main.c" <<'EOT' +#include <last.h> +EOT +if "$MCPU_CPP" --config-file "$base/config" \ + -isystem "$base/wrapper" "$base/last-main.c" \ + -o "$base/last.out" 2>"$base/last.err"; then + echo '#include_next unexpectedly found the current header again' >&2 + exit 1 +fi +grep "cannot find include_next file 'last.h'" "$base/last.err" >/dev/null diff --git a/tests/t0044-configured-search-order.sh b/tests/t0044-configured-search-order.sh new file mode 100755 index 0000000..980790f --- /dev/null +++ b/tests/t0044-configured-search-order.sh @@ -0,0 +1,114 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-config-order-$$" +mkdir -p \ + "$base/cmd-I" \ + "$base/cmd-isystem" \ + "$base/config-as" \ + "$base/config-user" \ + "$base/system/as" \ + "$base/cmd-after" \ + "$base/config-after" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/config" <<EOT +MCPU_CPP_AS_INCLUDE_PATH = $base/config-as; +MCPU_CPP_INCLUDE_PATH = $base/config-user; +MCPU_CPP_SYSTEM_INCLUDE_PATH = $base/system; +MCPU_CPP_AFTER_INCLUDE_PATH = $base/config-after; +EOT + +cat > "$base/main.c" <<'EOT' +#lang "as" +#include <same.h> +#endlang +EOT + +cat > "$base/cmd-I/same.h" <<'EOT' +from_explicit_I +#include_next <same.h> +EOT +cat > "$base/cmd-isystem/same.h" <<'EOT' +from_explicit_isystem +#include_next <same.h> +EOT +cat > "$base/config-as/same.h" <<'EOT' +from_config_language +#include_next <same.h> +EOT +cat > "$base/config-user/same.h" <<'EOT' +from_config_user +#include_next <same.h> +EOT +cat > "$base/system/as/same.h" <<'EOT' +from_system_language +#include_next <same.h> +EOT +cat > "$base/system/same.h" <<'EOT' +from_system_root +#include_next <same.h> +EOT +cat > "$base/cmd-after/same.h" <<'EOT' +from_explicit_idirafter +#include_next <same.h> +EOT +cat > "$base/config-after/same.h" <<'EOT' +from_config_after +EOT + +# Deliberately scramble command-line option order. Semantic classes, not +# argv order, define precedence. +"$MCPU_CPP" --config-file "$base/config" \ + -idirafter "$base/cmd-after" \ + -isystem "$base/cmd-isystem" \ + -I "$base/cmd-I" \ + "$base/main.c" -o "$base/out" + +awk ' + /from_explicit_I/ { a = NR } + /from_explicit_isystem/ { b = NR } + /from_config_language/ { c = NR } + /from_config_user/ { d = NR } + /from_system_language/ { e = NR } + /from_system_root/ { f = NR } + /from_explicit_idirafter/ { g = NR } + /from_config_after/ { h = NR } + END { exit !(a && a < b && b < c && c < d && d < e && e < f && f < g && g < h) } +' "$base/out" + +# An empty MCPU_CPP_SYSTEM_INCLUDE_PATH disables the complete configured +# system tree, including the automatically derived <lang> subdirectory. +cat > "$base/no-system.conf" <<EOT +MCPU_CPP_SYSTEM_INCLUDE_PATH = ; +EOT +cat > "$base/system-only.c" <<'EOT' +#lang "as" +#include <system-only.h> +#endlang +EOT +cat > "$base/system/as/system-only.h" <<'EOT' +system_only_header +EOT +if "$MCPU_CPP" --config-file "$base/no-system.conf" \ + "$base/system-only.c" -o "$base/no-system.out" 2>"$base/no-system.err"; then + echo 'empty MCPU_CPP_SYSTEM_INCLUDE_PATH did not disable system includes' >&2 + exit 1 +fi + +# -nostdinc suppresses the configured system tree for one invocation but does +# not suppress explicit -isystem or either after class. +cat > "$base/nostdinc.c" <<'EOT' +#lang "as" +#include <nostdinc.h> +#endlang +EOT +cat > "$base/system/as/nostdinc.h" <<'EOT' +wrong_configured_system +EOT +cat > "$base/cmd-after/nostdinc.h" <<'EOT' +from_after_with_nostdinc +EOT +"$MCPU_CPP" --config-file "$base/config" -nostdinc \ + -idirafter "$base/cmd-after" "$base/nostdinc.c" -o "$base/nostdinc.out" +grep '^from_after_with_nostdinc$' "$base/nostdinc.out" >/dev/null +! grep '^wrong_configured_system$' "$base/nostdinc.out" >/dev/null diff --git a/tests/t0045-search-dirs-verbose.sh b/tests/t0045-search-dirs-verbose.sh new file mode 100755 index 0000000..03a6393 --- /dev/null +++ b/tests/t0045-search-dirs-verbose.sh @@ -0,0 +1,89 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-search-dirs-$$" +mkdir -p \ + "$base/cmd-user" \ + "$base/cmd-system" \ + "$base/config-as" \ + "$base/config-user" \ + "$base/system" \ + "$base/cmd-after" \ + "$base/config-after" +trap 'rm -rf "$base"' EXIT HUP INT TERM +runtime_root=$(cd "$(dirname "$MCPU_CPP")/.." && pwd -P) + +cat > "$base/test.conf" <<EOT +MCPU_CPP_INCLUDE_PATH = $base/old-user; +MCPU_CPP_AS_INCLUDE_PATH = $base/config-as; +MCPU_CPP_INCLUDE_PATH = $base/config-user; +MCPU_CPP_SYSTEM_INCLUDE_PATH = $base/system; +MCPU_CPP_AFTER_INCLUDE_PATH = $base/config-after; +EOT + +"$MCPU_CPP" \ + --config-file "$base/test.conf" \ + -I "$base/cmd-user" \ + -isystem "$base/cmd-system" \ + -idirafter "$base/cmd-after" \ + -v -dsearch-dirs \ + > "$base/search.out" 2> "$base/verbose.out" + +cat > "$base/search.expected" <<EOT +search: $base/cmd-user +search: $base/cmd-system +search: $base/config-as +search: $base/config-user +search: $base/system/diff +search: $base/system/dift +search: $base/system/alg +search: $base/system/as +search: $base/system/avm +search: $base/system/acs +search: $base/system +search: $base/cmd-after +search: $base/config-after +EOT +cmp "$base/search.expected" "$base/search.out" + +# -v prints one effective value, not every assignment encountered while +# reading configuration files. +test "$(grep -c '^config: MCPU_CPP_INCLUDE_PATH=' "$base/verbose.out")" -eq 1 +grep "^config: MCPU_CPP_INCLUDE_PATH=$base/config-user$" "$base/verbose.out" >/dev/null +if grep "$base/old-user" "$base/verbose.out" >/dev/null; then + echo 'verbose output contains an overridden configuration value' >&2 + exit 1 +fi +grep "^config: MCPU_CPP_AS_INCLUDE_PATH=$base/config-as$" "$base/verbose.out" >/dev/null +grep "^config: MCPU_CPP_SYSTEM_INCLUDE_PATH=$base/system$" "$base/verbose.out" >/dev/null +grep "^config: MCPU_CPP_AFTER_INCLUDE_PATH=$base/config-after$" "$base/verbose.out" >/dev/null + +# Even when an explicitly selected configuration file is empty, -v and +# -dsearch-dirs must expose the runtime-derived installation system root. +: > "$base/empty.conf" +"$MCPU_CPP" --config-file "$base/empty.conf" -v -dsearch-dirs \ + > "$base/default-search.out" 2> "$base/default-verbose.out" +grep '^config: MCPU_CPP_SYSTEM_INCLUDE_PATH=' "$base/default-verbose.out" >/dev/null +grep "^search: $runtime_root/include$" "$base/default-search.out" >/dev/null + +# -nostdinc removes both the generated <lang> system directories and the +# configured system root from the directory dump, but keeps explicit -isystem. +"$MCPU_CPP" \ + --config-file "$base/test.conf" \ + -isystem "$base/cmd-system" \ + -nostdinc -dsearch-dirs > "$base/nostdinc.out" +grep "^search: $base/cmd-system$" "$base/nostdinc.out" >/dev/null +if grep "^search: $base/system" "$base/nostdinc.out" >/dev/null; then + echo '-nostdinc left configured system directories in -dsearch-dirs' >&2 + exit 1 +fi + +# A dump action stops preprocessing. An input name may be present but need +# not exist. +"$MCPU_CPP" --config-file "$base/test.conf" -dsearch-dirs \ + "$base/does-not-exist.c" > /dev/null + +if "$MCPU_CPP" --config-file "$base/test.conf" -dsearch-dirs -o "$base/out" \ + > /dev/null 2>&1; then + echo '-dsearch-dirs accepted an output file' >&2 + exit 1 +fi diff --git a/tests/t0046-pragma-once.sh b/tests/t0046-pragma-once.sh new file mode 100755 index 0000000..9429dd7 --- /dev/null +++ b/tests/t0046-pragma-once.sh @@ -0,0 +1,67 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-pragma-once-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/once.h" <<'EOT' +#pragma once +ONCE_BODY +EOT +ln -s once.h "$base/once-link.h" +ln "$base/once.h" "$base/once-hard.h" + +cat > "$base/self.h" <<'EOT' +#pragma once +SELF_BEGIN +#include "self.h" +SELF_END +EOT + +cat > "$base/inactive.h" <<'EOT' +#if 0 +#pragma once +#endif +INACTIVE_BODY +EOT + +cat > "$base/main.c" <<'EOT' +#include "once.h" +#include "once.h" +#include "once-link.h" +#include "once-hard.h" +#include "self.h" +#include "inactive.h" +#include "inactive.h" +#pragma pack(push, 1) +EOT + +"$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" + +# The same physical file is processed only once, even through a symbolic link +# and a hard link. #pragma once itself is consumed by the preprocessor. +test "$(grep -c '^ONCE_BODY$' "$base/out")" -eq 1 +! grep '^#pragma once$' "$base/out" >/dev/null + +# Marking happens as soon as the active directive is seen, so a file may safely +# include itself after #pragma once without recursive inclusion. +test "$(grep -c '^SELF_BEGIN$' "$base/out")" -eq 1 +test "$(grep -c '^SELF_END$' "$base/out")" -eq 1 + +# An inactive #pragma once has no effect. +test "$(grep -c '^INACTIVE_BODY$' "$base/out")" -eq 2 + +# Other pragmas are not consumed; they remain available to the later compiler. +grep '^#pragma pack(push, 1)$' "$base/out" >/dev/null + +# -dD does not make #pragma once reappear; unrelated pragmas are still kept. +"$MCPU_CPP" --no-config -dD "$base/main.c" -o "$base/dd.out" +! grep '^#pragma once$' "$base/dd.out" >/dev/null +grep '^#pragma pack(push, 1)$' "$base/dd.out" >/dev/null + +# A non-file input stream has no physical identity. The directive is simply +# consumed and must not turn stdin preprocessing into an error. +printf '#pragma once\nSTDIN_BODY\n' | \ + "$MCPU_CPP" --no-config > "$base/stdin.out" +grep '^STDIN_BODY$' "$base/stdin.out" >/dev/null +! grep '^#pragma once$' "$base/stdin.out" >/dev/null diff --git a/tests/t0047-dependencies.sh b/tests/t0047-dependencies.sh new file mode 100755 index 0000000..87ecb63 --- /dev/null +++ b/tests/t0047-dependencies.sh @@ -0,0 +1,140 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-deps-$$" +mkdir -p "$base/user" "$base/sys" "$base/cfg-user" "$base/cfg-sys" "$base/after" "$base/cfg-after" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/config.conf" <<EOT +MCPU_CPP_INCLUDE_PATH = $base/cfg-user; +MCPU_CPP_SYSTEM_INCLUDE_PATH = $base/cfg-sys; +MCPU_CPP_AFTER_INCLUDE_PATH = $base/cfg-after; +EOT + +cat > "$base/main.c" <<'EOT' +#include "local.h" +#include "local-alias.h" +#include <user.h> +#include <cfg-user.h> +#include "sys-explicit.h" +#include <sys-config.h> +#include <after-explicit.h> +#include <after-config.h> +#include <same.h> +#include <shared.h> +SHOULD_NOT_APPEAR_IN_DEP_OUTPUT +EOT + +cat > "$base/local.h" <<'EOT' +#pragma once +#line 100 "logical-local.h" +LOCAL_BODY +EOT +ln "$base/local.h" "$base/local-alias.h" + +cat > "$base/user/user.h" <<'EOT' +USER_HEADER +EOT +cat > "$base/cfg-user/cfg-user.h" <<'EOT' +CONFIG_USER_HEADER +EOT +cat > "$base/user/shared.h" <<'EOT' +SHARED_USER_HEADER +EOT +cat > "$base/user/transitive-only.h" <<'EOT' +TRANSITIVE_USER_HEADER +EOT + +cat > "$base/sys/sys-explicit.h" <<'EOT' +#include <transitive-only.h> +#include <shared.h> +#include "sys-child.h" +EOT +cat > "$base/sys/sys-child.h" <<'EOT' +SYSTEM_CHILD +EOT +cat > "$base/cfg-sys/sys-config.h" <<'EOT' +SYSTEM_CONFIG +EOT +cat > "$base/after/after-explicit.h" <<'EOT' +AFTER_EXPLICIT +EOT +cat > "$base/cfg-after/after-config.h" <<'EOT' +AFTER_CONFIG +EOT + +cat > "$base/user/same.h" <<'EOT' +USER_WRAPPER +#include_next <same.h> +EOT +cat > "$base/sys/same.h" <<'EOT' +SYSTEM_WRAPPED +EOT + +"$MCPU_CPP" --config-file "$base/config.conf" \ + -I "$base/user" -isystem "$base/sys" -idirafter "$base/after" \ + -M "$base/main.c" > "$base/M.out" + +# -M emits only a make rule and includes every physical dependency. +grep "^main\\.o:" "$base/M.out" >/dev/null +! grep 'SHOULD_NOT_APPEAR_IN_DEP_OUTPUT' "$base/M.out" >/dev/null +for f in \ + "$base/main.c" "$base/local.h" "$base/user/user.h" \ + "$base/cfg-user/cfg-user.h" "$base/sys/sys-explicit.h" \ + "$base/user/transitive-only.h" "$base/user/shared.h" \ + "$base/sys/sys-child.h" "$base/cfg-sys/sys-config.h" \ + "$base/after/after-explicit.h" "$base/cfg-after/after-config.h" \ + "$base/user/same.h" "$base/sys/same.h" +do + grep -F "$f" "$base/M.out" >/dev/null || { + echo "-M omitted dependency: $f" >&2 + exit 1 + } +done + +# local-alias.h is a hard link to local.h and must not create a duplicate +# physical dependency. The logical #line name is not a dependency either. +! grep -F "$base/local-alias.h" "$base/M.out" >/dev/null +! grep -F 'logical-local.h' "$base/M.out" >/dev/null + +"$MCPU_CPP" --config-file "$base/config.conf" \ + -I "$base/user" -isystem "$base/sys" -idirafter "$base/after" \ + -MM "$base/main.c" > "$base/MM.out" + +grep "^main\\.o:" "$base/MM.out" >/dev/null +for f in "$base/main.c" "$base/local.h" "$base/user/user.h" \ + "$base/cfg-user/cfg-user.h" "$base/user/same.h" "$base/user/shared.h" +do + grep -F "$f" "$base/MM.out" >/dev/null || { + echo "-MM omitted user dependency: $f" >&2 + exit 1 + } +done + +# Quoting style does not define system-ness. sys-explicit.h was included +# with quotes but found in -isystem, and all descendants reachable only from +# that system header are excluded. -idirafter/config AFTER are system class. +for f in "$base/sys/sys-explicit.h" "$base/user/transitive-only.h" \ + "$base/sys/sys-child.h" "$base/cfg-sys/sys-config.h" \ + "$base/after/after-explicit.h" "$base/cfg-after/after-config.h" \ + "$base/sys/same.h" +do + if grep -F "$f" "$base/MM.out" >/dev/null; then + echo "-MM kept system dependency: $f" >&2 + exit 1 + fi +done + +# The same physical shared.h is first reached transitively from a system +# header, then directly from main. The direct user inclusion makes it a user +# dependency, so -MM must retain it (checked above). + +# Normal mcpu-cpp output-file syntax remains usable for dependency output. +"$MCPU_CPP" --config-file "$base/config.conf" -I "$base/user" \ + -isystem "$base/sys" -idirafter "$base/after" \ + -MM "$base/main.c" -o "$base/deps.mk" +test -s "$base/deps.mk" +grep '^main\.o:' "$base/deps.mk" >/dev/null + +# stdin follows GNU cpp's useful default target convention. +printf 'int x;\n' | "$MCPU_CPP" --no-config -M > "$base/stdin.dep" +grep '^-: -$' "$base/stdin.dep" >/dev/null diff --git a/tests/t0048-no-config-defaults.sh b/tests/t0048-no-config-defaults.sh new file mode 100755 index 0000000..a6d2c75 --- /dev/null +++ b/tests/t0048-no-config-defaults.sh @@ -0,0 +1,73 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-no-config-defaults-$$" +mkdir -p "$base/home/.mcpu" "$base/I" "$base/isystem" "$base/after" +trap 'rm -rf "$base"' EXIT HUP INT TERM +runtime_root=$(cd "$(dirname "$MCPU_CPP")/.." && pwd -P) + +cat > "$base/home/.mcpu/mcpu-cpp.conf" <<EOT +MCPU_CPP_INCLUDE_PATH = $base/forbidden-user; +MCPU_CPP_SYSTEM_INCLUDE_PATH = $base/forbidden-system; +MCPU_CPP_AFTER_INCLUDE_PATH = $base/forbidden-after; +EOT + +# --no-config skips every configuration file, but the runtime-derived installation +# default remains the effective system root. +HOME="$base/home" "$MCPU_CPP" --no-config -dsearch-dirs > "$base/search.out" +grep "^search: $runtime_root/include/diff$" "$base/search.out" >/dev/null +grep "^search: $runtime_root/include/dift$" "$base/search.out" >/dev/null +grep "^search: $runtime_root/include/alg$" "$base/search.out" >/dev/null +grep "^search: $runtime_root/include/as$" "$base/search.out" >/dev/null +grep "^search: $runtime_root/include/avm$" "$base/search.out" >/dev/null +grep "^search: $runtime_root/include/acs$" "$base/search.out" >/dev/null +grep "^search: $runtime_root/include$" "$base/search.out" >/dev/null +if grep "$base/forbidden" "$base/search.out" >/dev/null; then + echo '--no-config read the user configuration file' >&2 + exit 1 +fi + +# The same runtime-derived value is visible through -dconfig and -v. +HOME="$base/home" "$MCPU_CPP" --no-config -dconfig > "$base/config.out" +grep "^MCPU_CPP_SYSTEM_INCLUDE_PATH = $runtime_root/include;$" "$base/config.out" >/dev/null +if grep "$base/forbidden" "$base/config.out" >/dev/null; then + echo '--no-config leaked user configuration into -dconfig' >&2 + exit 1 +fi + +HOME="$base/home" "$MCPU_CPP" --no-config -v -dsearch-dirs \ + > "$base/verbose-search.out" 2> "$base/verbose.out" +grep "^config: MCPU_CPP_SYSTEM_INCLUDE_PATH=$runtime_root/include$" "$base/verbose.out" >/dev/null +if grep "$base/forbidden" "$base/verbose.out" >/dev/null; then + echo '--no-config leaked user configuration into -v' >&2 + exit 1 +fi + +# Explicit command-line classes retain their normative priority around the +# runtime-derived system root. +HOME="$base/home" "$MCPU_CPP" --no-config \ + -I "$base/I" -isystem "$base/isystem" -idirafter "$base/after" \ + -dsearch-dirs > "$base/ordered.out" +first=$(sed -n '1p' "$base/ordered.out") +second=$(sed -n '2p' "$base/ordered.out") +last=$(tail -n 1 "$base/ordered.out") +test "$first" = "search: $base/I" +test "$second" = "search: $base/isystem" +test "$last" = "search: $base/after" +grep "^search: $runtime_root/include$" "$base/ordered.out" >/dev/null + +# -nostdinc, not --no-config, is the switch that suppresses the runtime-derived +# standard-system include tree. Explicit classes remain visible. +HOME="$base/home" "$MCPU_CPP" --no-config -nostdinc \ + -I "$base/I" -isystem "$base/isystem" -idirafter "$base/after" \ + -dsearch-dirs > "$base/nostdinc.out" +cat > "$base/nostdinc.expected" <<EOT +search: $base/I +search: $base/isystem +search: $base/after +EOT +cmp "$base/nostdinc.expected" "$base/nostdinc.out" + +# With no explicit directories, --no-config -nostdinc has no search entries. +HOME="$base/home" "$MCPU_CPP" --no-config -nostdinc -dsearch-dirs \ + > "$base/empty.out" +test ! -s "$base/empty.out" diff --git a/tests/t0049-relocatable-root.sh b/tests/t0049-relocatable-root.sh new file mode 100755 index 0000000..d64a9b8 --- /dev/null +++ b/tests/t0049-relocatable-root.sh @@ -0,0 +1,55 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-relocatable-root-$$" +root_a="$base/mcpu-a" +root_b="$base/mcpu-b" +mkdir -p "$root_a/bin" "$root_a/etc" "$root_a/include/diff" "$base/home" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cp "$MCPU_CPP" "$root_a/bin/mcpu-cpp" +chmod +x "$root_a/bin/mcpu-cpp" + +cat > "$root_a/include/diff/relocated.h" <<'EOT' +int relocated_header = 37; +EOT + +cat > "$root_a/etc/mcpu-cpp.conf" <<'EOT' +MCPU_CPP_RELOCATION_SENTINEL = runtime-config; +EOT + +# The copied executable first derives its defaults from its new physical root. +HOME="$base/home" "$root_a/bin/mcpu-cpp" --no-config -dsearch-dirs \ + > "$base/root-a.search" +grep "^search: $root_a/include/diff$" "$base/root-a.search" >/dev/null +grep "^search: $root_a/include$" "$base/root-a.search" >/dev/null + +# Move the complete MCPU tree without reconfiguring or rebuilding anything. +mv "$root_a" "$root_b" + +HOME="$base/home" "$root_b/bin/mcpu-cpp" --no-config -dsearch-dirs \ + > "$base/root-b.search" +grep "^search: $root_b/include/diff$" "$base/root-b.search" >/dev/null +grep "^search: $root_b/include$" "$base/root-b.search" >/dev/null +if grep "$root_a" "$base/root-b.search" >/dev/null; then + echo 'relocated mcpu-cpp retained its previous installation root' >&2 + exit 1 +fi + +cat > "$base/main.c" <<'EOT' +#lang "diff" +#include <relocated.h> +#endlang +EOT +HOME="$base/home" "$root_b/bin/mcpu-cpp" --no-config \ + "$base/main.c" -o "$base/main.out" +grep '^int relocated_header = 37;$' "$base/main.out" >/dev/null + +# The packaged configuration is found relative to the relocated root as well. +HOME="$base/home" "$root_b/bin/mcpu-cpp" -dconfig > "$base/config.out" +grep '^MCPU_CPP_RELOCATION_SENTINEL = runtime-config;$' "$base/config.out" >/dev/null + +# Invoking through a symbolic link must still resolve the physical executable +# and therefore the same relocated MCPU root. +ln -s "$root_b/bin/mcpu-cpp" "$base/mcpu-cpp" +HOME="$base/home" "$base/mcpu-cpp" --no-config -dconfig > "$base/link.out" +grep "^MCPU_CPP_SYSTEM_INCLUDE_PATH = $root_b/include;$" "$base/link.out" >/dev/null diff --git a/tests/t0050-dependency-side-effects.sh b/tests/t0050-dependency-side-effects.sh new file mode 100755 index 0000000..67cbcf6 --- /dev/null +++ b/tests/t0050-dependency-side-effects.sh @@ -0,0 +1,136 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-dep-side-effects-$$" +mkdir -p "$base/src" "$base/user" "$base/sys" "$base/out" "$base/stdin" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/src/main.c" <<'EOT' +#include "user.h" +#include <system.h> +MAIN_BODY +EOT +cat > "$base/user/user.h" <<'EOT' +USER_BODY +EOT +cat > "$base/sys/system.h" <<'EOT' +#include "system-child.h" +SYSTEM_BODY +EOT +cat > "$base/sys/system-child.h" <<'EOT' +SYSTEM_CHILD_BODY +EOT + +# -MD keeps preprocessing output and writes basename.d in the current +# directory when no -o/-MF is present. The input directory is not copied to +# the default dependency filename. +( + cd "$base" + "$MCPU_CPP" --no-config -I "$base/user" -isystem "$base/sys" \ + -MD "$base/src/main.c" > md.out +) +test -s "$base/md.out" +grep 'MAIN_BODY' "$base/md.out" >/dev/null +test -s "$base/main.d" +grep '^main\.o:' "$base/main.d" >/dev/null +for f in "$base/src/main.c" "$base/user/user.h" \ + "$base/sys/system.h" "$base/sys/system-child.h" +do + grep -F "$f" "$base/main.d" >/dev/null || { + echo "-MD omitted dependency: $f" >&2 + exit 1 + } +done + +# -MMD has the same side-effect behavior but applies the already established +# -MM user-dependency filter. +rm -f "$base/main.d" +( + cd "$base" + "$MCPU_CPP" --no-config -I "$base/user" -isystem "$base/sys" \ + -MMD "$base/src/main.c" > mmd.out +) +test -s "$base/mmd.out" +grep 'MAIN_BODY' "$base/mmd.out" >/dev/null +test -s "$base/main.d" +grep -F "$base/src/main.c" "$base/main.d" >/dev/null +grep -F "$base/user/user.h" "$base/main.d" >/dev/null +! grep -F "$base/sys/system.h" "$base/main.d" >/dev/null +! grep -F "$base/sys/system-child.h" "$base/main.d" >/dev/null + +# With ordinary -o, preprocessing goes to that file and the automatic +# dependency filename is obtained by replacing its suffix with .d. +rm -f "$base/main.d" "$base/out/result.i" "$base/out/result.d" +"$MCPU_CPP" --no-config -I "$base/user" -isystem "$base/sys" \ + -MD "$base/src/main.c" -o "$base/out/result.i" +test -s "$base/out/result.i" +grep 'MAIN_BODY' "$base/out/result.i" >/dev/null +test -s "$base/out/result.d" +grep '^main\.o:' "$base/out/result.d" >/dev/null +test ! -e "$base/main.d" + +# -MF overrides automatic dependency naming while leaving normal preprocessing +# output semantics untouched. +rm -f "$base/custom.d" "$base/main.d" +( + cd "$base" + "$MCPU_CPP" --no-config -I "$base/user" -isystem "$base/sys" \ + -MD -MF custom.d "$base/src/main.c" > mf.out +) +test -s "$base/mf.out" +grep 'MAIN_BODY' "$base/mf.out" >/dev/null +test -s "$base/custom.d" +test ! -e "$base/main.d" + +# Attached -MFfile spelling is accepted as by GNU cpp. +rm -f "$base/attached.d" +( + cd "$base" + "$MCPU_CPP" --no-config -I "$base/user" -isystem "$base/sys" \ + -MMD -MFattached.d "$base/src/main.c" > attached.out +) +test -s "$base/attached.d" +! grep -F "$base/sys/system.h" "$base/attached.d" >/dev/null + +# -MF also redirects the already implemented dependency-only -M/-MM modes. +rm -f "$base/only.d" "$base/only.out" +"$MCPU_CPP" --no-config -I "$base/user" -isystem "$base/sys" \ + -M -MF "$base/only.d" "$base/src/main.c" > "$base/only.out" +test ! -s "$base/only.out" +test -s "$base/only.d" +grep -F "$base/sys/system.h" "$base/only.d" >/dev/null + +rm -f "$base/only-user.d" "$base/only-user.out" +"$MCPU_CPP" --no-config -I "$base/user" -isystem "$base/sys" \ + -MM -MF "$base/only-user.d" "$base/src/main.c" > "$base/only-user.out" +test ! -s "$base/only-user.out" +test -s "$base/only-user.d" +! grep -F "$base/sys/system.h" "$base/only-user.d" >/dev/null + +# -MF - means dependency stdout. Put normal preprocessing in a regular -o +# file so the two streams are easy to verify independently. +rm -f "$base/out/stdout.i" "$base/out/stdout.d" +"$MCPU_CPP" --no-config -I "$base/user" -isystem "$base/sys" \ + -MMD -MF - "$base/src/main.c" -o "$base/out/stdout.i" \ + > "$base/mf-dash.out" +test -s "$base/out/stdout.i" +grep 'MAIN_BODY' "$base/out/stdout.i" >/dev/null +grep '^main\.o:' "$base/mf-dash.out" >/dev/null +test ! -e "$base/out/stdout.d" + +# stdin follows the existing '-' target convention and gets '-.d' when -MD +# needs an automatic dependency filename. +( + cd "$base/stdin" + printf 'STDIN_BODY\n' | "$MCPU_CPP" --no-config -MD > stdout.i +) +test -s "$base/stdin/stdout.i" +grep 'STDIN_BODY' "$base/stdin/stdout.i" >/dev/null +test -s "$base/stdin/-.d" +grep '^-: -$' "$base/stdin/-.d" >/dev/null + +# -MF alone is a command-line error: there is no dependency mode to redirect. +if "$MCPU_CPP" --no-config -MF "$base/orphan.d" "$base/src/main.c" \ + > /dev/null 2>&1; then + echo "-MF without a dependency mode unexpectedly succeeded" >&2 + exit 1 +fi diff --git a/tests/t0051-dump-macro-origin.sh b/tests/t0051-dump-macro-origin.sh new file mode 100755 index 0000000..1a2c504 --- /dev/null +++ b/tests/t0051-dump-macro-origin.sh @@ -0,0 +1,63 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-dump-origin-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/main.c" <<'EOT' +#define Z_USER 62 +#define A_USER 37 +#define ФУНКЦИЯ(x) x + 1 +#undef __MCPU_CPP_VERSION__ +EOT + +# -dM contains only ordinary final-state macros. Source and command-line +# definitions are ordinary; built-ins are not. +"$MCPU_CPP" --no-config -dM -DCMD_USER=91 "$base/main.c" > "$base/dM" +grep -Fx '#define A_USER 37' "$base/dM" >/dev/null +grep -Fx '#define CMD_USER 91' "$base/dM" >/dev/null +grep -Fx '#define Z_USER 62' "$base/dM" >/dev/null +grep -Fx '#define ФУНКЦИЯ(x) x + 1' "$base/dM" >/dev/null +if grep '^#define _ARCH_MCPU ' "$base/dM" >/dev/null; then exit 1; fi +if grep '^#define __MCPU_' "$base/dM" >/dev/null; then exit 1; fi +LC_ALL=C sort -c "$base/dM" + +# Empty input has no ordinary definitions. +"$MCPU_CPP" --no-config -dM < /dev/null > "$base/empty-dM" +test ! -s "$base/empty-dM" + +# -dMP contains predefined macros first and ordinary macros second. Both +# groups are deterministic and the #undef above removes the version builtin. +"$MCPU_CPP" --no-config -dMP -DCMD_USER=91 "$base/main.c" > "$base/dMP" +grep -Fx '#define _ARCH_MCPU 1' "$base/dMP" >/dev/null +grep -Fx '#define A_USER 37' "$base/dMP" >/dev/null +grep -Fx '#define CMD_USER 91' "$base/dMP" >/dev/null +grep -Fx '#define Z_USER 62' "$base/dMP" >/dev/null +grep -Fx '#define ФУНКЦИЯ(x) x + 1' "$base/dMP" >/dev/null +if grep '^#define __MCPU_CPP_VERSION__ ' "$base/dMP" >/dev/null; then exit 1; fi + +predefined_line=`grep -n '^#define _ARCH_MCPU 1$' "$base/dMP" | sed 's/:.*//'` +ordinary_line=`grep -n '^#define A_USER 37$' "$base/dMP" | sed 's/:.*//'` +test "$predefined_line" -lt "$ordinary_line" + +# Verify ordering inside the ordinary tail explicitly. +tail -n +"$ordinary_line" "$base/dMP" > "$base/ordinary-tail" +LC_ALL=C sort -c "$base/ordinary-tail" + +# A former builtin name, if explicitly redefined after #undef, is an ordinary +# macro because origin follows the current definition rather than the spelling. +cat > "$base/redefine.c" <<'EOT' +#undef _ARCH_MCPU +#define _ARCH_MCPU 777 +#define LOCAL 1 +EOT +"$MCPU_CPP" --no-config -dM "$base/redefine.c" > "$base/redefine-dM" +grep -Fx '#define _ARCH_MCPU 777' "$base/redefine-dM" >/dev/null +"$MCPU_CPP" --no-config -dMP "$base/redefine.c" > "$base/redefine-dMP" +grep -Fx '#define _ARCH_MCPU 777' "$base/redefine-dMP" >/dev/null +test "`grep -c '^#define _ARCH_MCPU ' "$base/redefine-dMP"`" -eq 1 + +# Context-dependent special builtins remain outside the static dump. +for name in __FILE__ __LINE__ __DATE__ __TIME__ __BASE_FILE__ __INCLUDE_LEVEL__; do + if grep "^#define ${name}\\>" "$base/dMP" >/dev/null; then exit 1; fi +done diff --git a/tests/t0052-warning-control.sh b/tests/t0052-warning-control.sh new file mode 100755 index 0000000..8214d96 --- /dev/null +++ b/tests/t0052-warning-control.sh @@ -0,0 +1,108 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-warning-control-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/comments.c" <<'EOT' +/* outer /* nested */ +// continued \ +comment +ok +EOT + +# Comment warnings are optional and disabled by default. +"$MCPU_CPP" --no-config "$base/comments.c" -o "$base/default.out" 2>"$base/default.err" +test ! -s "$base/default.err" +grep '^ok$' "$base/default.out" >/dev/null + +# -Wcomment and -Wcomments are synonyms. +for option in -Wcomment -Wcomments -Wall; do + "$MCPU_CPP" --no-config "$option" "$base/comments.c" \ + -o "$base/comment.out" 2>"$base/comment.err" + test "`grep -c 'warning: \"/\*\" within comment' "$base/comment.err"`" -eq 1 + test "`grep -c 'warning: multi-line comment' "$base/comment.err"`" -eq 1 +done + +# A specific -Wno-comment is stronger than the -Wall group regardless of order. +for options in '-Wall -Wno-comment' '-Wno-comment -Wall' \ + '-Wall -Wno-comments' '-Wno-comments -Wall'; do + # shellcheck disable=SC2086 + "$MCPU_CPP" --no-config $options "$base/comments.c" \ + -o "$base/no-comment.out" 2>"$base/no-comment.err" + test ! -s "$base/no-comment.err" +done + +# Same-specificity comment options use the last occurrence. +"$MCPU_CPP" --no-config -Wno-comment -Wcomment "$base/comments.c" \ + -o "$base/comment-last.out" 2>"$base/comment-last.err" +grep 'warning: multi-line comment' "$base/comment-last.err" >/dev/null + +"$MCPU_CPP" --no-config -Wcomment -Wno-comment "$base/comments.c" \ + -o "$base/no-comment-last.out" 2>"$base/no-comment-last.err" +test ! -s "$base/no-comment-last.err" + +# -Werror does not enable optional comment diagnostics by itself. +"$MCPU_CPP" --no-config -Werror "$base/comments.c" \ + -o "$base/werror-only.out" 2>"$base/werror-only.err" +test ! -s "$base/werror-only.err" + +# When comment diagnostics are enabled, -Werror promotes them to errors. +if "$MCPU_CPP" --no-config -Wcomment -Werror "$base/comments.c" \ + -o "$base/comment-error.out" 2>"$base/comment-error.err"; then + echo '-Wcomment -Werror did not fail' >&2 + exit 1 +fi +grep 'error: "/\*" within comment' "$base/comment-error.err" >/dev/null + +# -Werror/-Wno-error have equal specificity; the last occurrence wins. +cat > "$base/directive.c" <<'EOT' +#warning requested warning +ok +EOT +"$MCPU_CPP" --no-config -Werror -Wno-error "$base/directive.c" \ + -o "$base/no-error.out" 2>"$base/no-error.err" +grep 'warning: #warning requested warning$' "$base/no-error.err" >/dev/null + +if "$MCPU_CPP" --no-config -Wno-error -Werror "$base/directive.c" \ + -o "$base/error-last.out" 2>"$base/error-last.err"; then + echo '-Wno-error -Werror did not fail' >&2 + exit 1 +fi +grep 'error: #warning requested warning$' "$base/error-last.err" >/dev/null + +# Existing always-issued warnings are also promoted by -Werror. +cat > "$base/width.c" <<'EOT' +#if 1z24 == 1 +ok +#endif +EOT +if "$MCPU_CPP" --no-config -Werror "$base/width.c" \ + -o "$base/width.out" 2>"$base/width.err"; then + echo 'zNNN warning was not promoted by -Werror' >&2 + exit 1 +fi +grep 'error: invalid zNNN integer-width suffix; suffix ignored$' "$base/width.err" >/dev/null + +cat > "$base/redefine.c" <<'EOT' +#define VALUE 1 +#define VALUE 2 +EOT +if "$MCPU_CPP" --no-config -Werror "$base/redefine.c" \ + -o "$base/redefine.out" 2>"$base/redefine.err"; then + echo 'macro redefinition warning was not promoted by -Werror' >&2 + exit 1 +fi +grep "error: macro 'VALUE' redefined$" "$base/redefine.err" >/dev/null + +cat > "$base/paste.c" <<'EOT' +#define BAD(X) X ## + +BAD(foo) +EOT +if "$MCPU_CPP" --no-config -Werror "$base/paste.c" \ + -o "$base/paste.out" 2>"$base/paste.err"; then + echo 'invalid paste warning was not promoted by -Werror' >&2 + exit 1 +fi +grep 'error: pasting "foo" and "+" does not give a valid preprocessing token$' \ + "$base/paste.err" >/dev/null diff --git a/tests/t0053-interface-cleanup.sh b/tests/t0053-interface-cleanup.sh new file mode 100755 index 0000000..80d5410 --- /dev/null +++ b/tests/t0053-interface-cleanup.sh @@ -0,0 +1,34 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-interface-cleanup-$$" +mkdir -p "$base/wrapper" "$base/after" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +# --version identifies the independent MCPU languages preprocessor. +"$MCPU_CPP" --version > "$base/version" +printf '%s\n' 'mcpu-cpp 1.0.2' 'MCPU languages preprocessor' > "$base/version.expected" +cmp "$base/version.expected" "$base/version" + +# --help contains the current public interface, including --object-suffix, +# and puts the stdin/stdout notes after the option list. +"$MCPU_CPP" --help > "$base/help" +grep '^mcpu-cpp 1\.0\.2$' "$base/help" >/dev/null +grep '^MCPU languages preprocessor$' "$base/help" >/dev/null +grep '^ --object-suffix SFX set object suffix used for dependency targets$' "$base/help" >/dev/null +help_version_line=$(grep -n '^ --version ' "$base/help" | cut -d: -f1) +input_note_line=$(grep -n "^If input is omitted or '-', read standard input\.$" "$base/help" | cut -d: -f1) +test "$input_note_line" -gt "$help_version_line" + +grep "^If output is omitted or '-', write standard output\.$" "$base/help" >/dev/null + +# --object-suffix requires an argument and changes the default dependency target. +cat > "$base/object.c" <<'SRC' +int x; +SRC +if "$MCPU_CPP" --no-config --object-suffix > /dev/null 2> "$base/object.err"; then + echo '--object-suffix unexpectedly accepted without an argument' >&2 + exit 1 +fi +grep "argument missing after '--object-suffix'" "$base/object.err" >/dev/null +"$MCPU_CPP" --no-config --object-suffix .obj -M "$base/object.c" > "$base/object.d" +grep '^object\.obj:' "$base/object.d" >/dev/null diff --git a/tests/t0054-inhibit-warnings.sh b/tests/t0054-inhibit-warnings.sh new file mode 100755 index 0000000..339d098 --- /dev/null +++ b/tests/t0054-inhibit-warnings.sh @@ -0,0 +1,81 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-inhibit-warnings-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/directive.c" <<'EOT' +#warning requested warning +ok +EOT + +# -w suppresses #warning completely. +"$MCPU_CPP" --no-config -w "$base/directive.c" \ + -o "$base/directive.out" 2>"$base/directive.err" +test ! -s "$base/directive.err" +grep '^ok$' "$base/directive.out" >/dev/null + +# -w is a global warning gate, so command-line order relative to -Werror +# does not matter and there is no warning left to promote. +for options in '-w -Werror' '-Werror -w'; do + # shellcheck disable=SC2086 + "$MCPU_CPP" --no-config $options "$base/directive.c" \ + -o "$base/werror.out" 2>"$base/werror.err" + test ! -s "$base/werror.err" +done + +cat > "$base/width.c" <<'EOT' +#if 1z24 == 1 +ok +#endif +EOT +"$MCPU_CPP" --no-config -w "$base/width.c" \ + -o "$base/width.out" 2>"$base/width.err" +test ! -s "$base/width.err" +grep '^ok$' "$base/width.out" >/dev/null + +cat > "$base/redefine.c" <<'EOT' +#define VALUE 1 +#define VALUE 2 +VALUE +EOT +"$MCPU_CPP" --no-config -w "$base/redefine.c" \ + -o "$base/redefine.out" 2>"$base/redefine.err" +test ! -s "$base/redefine.err" +grep '^2$' "$base/redefine.out" >/dev/null + +cat > "$base/paste.c" <<'EOT' +#define BAD(X) X ## + +BAD(foo) +EOT +"$MCPU_CPP" --no-config -w "$base/paste.c" \ + -o "$base/paste.out" 2>"$base/paste.err" +test ! -s "$base/paste.err" + +cat > "$base/comments.c" <<'EOT' +/* outer /* nested */ +// continued \ +comment +ok +EOT + +# A specific warning class may be enabled, but -w still suppresses its output. +# Command-line order does not matter. +for options in '-w -Wcomment' '-Wcomment -w' '-w -Wall' '-Wall -w'; do + # shellcheck disable=SC2086 + "$MCPU_CPP" --no-config $options "$base/comments.c" \ + -o "$base/comments.out" 2>"$base/comments.err" + test ! -s "$base/comments.err" + grep '^ok$' "$base/comments.out" >/dev/null +done + +# Errors are not warnings and must remain visible/fatal under -w. +cat > "$base/error.c" <<'EOT' +#error requested error +EOT +if "$MCPU_CPP" --no-config -w "$base/error.c" \ + -o "$base/error.out" 2>"$base/error.err"; then + echo '-w suppressed a real preprocessing error' >&2 + exit 1 +fi +grep 'error: #error requested error$' "$base/error.err" >/dev/null diff --git a/tests/t0055-forced-files.sh b/tests/t0055-forced-files.sh new file mode 100755 index 0000000..454fa55 --- /dev/null +++ b/tests/t0055-forced-files.sh @@ -0,0 +1,242 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-forced-files-$$" +mkdir -p "$base/project/src" "$base/project/user" "$base/project/sys" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/project/defs1.h" <<'EOT' +IMACROS1_BODY_MUST_NOT_APPEAR +#if CLI_VALUE != 9 +#error command-line macros were not applied before -imacros +#endif +#if __INCLUDE_LEVEL__ != 1 +#error -imacros must run at include level 1 +#endif +#define ORDER_VALUE 1 +#define FROM_IMACROS1 11 +#include "imacros-child.h" +#lang "as" +#define FROM_AS_LANG 13 +#endlang +EOT + +cat > "$base/project/imacros-child.h" <<'EOT' +IMACROS_CHILD_BODY_MUST_NOT_APPEAR +#if __INCLUDE_LEVEL__ != 2 +#error nested include from -imacros must run at include level 2 +#endif +#define FROM_IMACROS_CHILD 12 +EOT + +cat > "$base/project/defs2.h" <<'EOT' +IMACROS2_BODY_MUST_NOT_APPEAR +#if ORDER_VALUE != 1 +#error -imacros command-line order is broken +#endif +#if FROM_IMACROS_CHILD != 12 || FROM_AS_LANG != 13 +#error -imacros did not retain preprocessing state +#endif +#undef ORDER_VALUE +#define ORDER_VALUE 2 +EOT + +cat > "$base/project/a.h" <<'EOT' +#if ORDER_VALUE != 2 +#error all -imacros files must precede all -include files +#endif +INCLUDE_A_BODY +FORCED_LEVEL __INCLUDE_LEVEL__ +FORCED_BASE __BASE_FILE__ +#undef ORDER_VALUE +#define ORDER_VALUE 3 +EOT + +cat > "$base/project/b.h" <<'EOT' +#if ORDER_VALUE != 3 +#error -include command-line order is broken +#endif +INCLUDE_B_BODY +#undef ORDER_VALUE +#define ORDER_VALUE 4 +EOT + +cat > "$base/project/src/main.c" <<'EOT' +#if ORDER_VALUE != 4 +#error forced files did not complete before primary input +#endif +MAIN_BODY +MAIN_LEVEL __INCLUDE_LEVEL__ +FROM_IMACROS1 FROM_IMACROS_CHILD FROM_AS_LANG CLI_VALUE +EOT + +# Deliberately interleave -include and -imacros. The semantic order is all +# command-line -D/-U first, then all -imacros in their own argv order, then all +# -include in their own argv order, and only then the primary input. +( + cd "$base/project" + "$MCPU_CPP" --no-config \ + -include a.h -DCLI_VALUE=9 -imacros defs1.h \ + -include b.h -imacros defs2.h \ + src/main.c -o "$base/order.out" +) + +! grep 'IMACROS1_BODY_MUST_NOT_APPEAR' "$base/order.out" >/dev/null +! grep 'IMACROS2_BODY_MUST_NOT_APPEAR' "$base/order.out" >/dev/null +! grep 'IMACROS_CHILD_BODY_MUST_NOT_APPEAR' "$base/order.out" >/dev/null +! grep '^#lang ' "$base/order.out" >/dev/null +! grep '^#endlang' "$base/order.out" >/dev/null + +awk ' + /^INCLUDE_A_BODY$/ { a = NR } + /^INCLUDE_B_BODY$/ { b = NR } + /^MAIN_BODY$/ { c = NR } + END { exit !(a && a < b && b < c) } +' "$base/order.out" +grep '^11 12 13 9$' "$base/order.out" >/dev/null +grep '^FORCED_LEVEL 1$' "$base/order.out" >/dev/null +grep '^FORCED_BASE "src/main.c"$' "$base/order.out" >/dev/null +grep '^MAIN_LEVEL 0$' "$base/order.out" >/dev/null +# Each visible forced include returns to the primary file in the line-marker +# stack before another forced include or the primary body continues. +test `grep -c '^# 1 "src/main.c" 2$' "$base/order.out"` -eq 2 + +# A forced command-line file is searched from the current working directory, +# not from the physical directory of the primary input. +cat > "$base/project/cwd-first.h" <<'EOT' +CWD_FORCED_HEADER +EOT +cat > "$base/project/src/cwd-first.h" <<'EOT' +PRIMARY_DIRECTORY_MUST_NOT_WIN +EOT +cat > "$base/project/src/cwd-main.c" <<'EOT' +CWD_MAIN +EOT +( + cd "$base/project" + "$MCPU_CPP" --no-config -include cwd-first.h src/cwd-main.c \ + -o "$base/cwd.out" +) +grep '^CWD_FORCED_HEADER$' "$base/cwd.out" >/dev/null +! grep 'PRIMARY_DIRECTORY_MUST_NOT_WIN' "$base/cwd.out" >/dev/null + +# After CWD, the ordinary quoted include chain is used. A forced file found +# through -I retains its physical provenance, so quoted includes inside it are +# resolved relative to that file's own directory. +cat > "$base/project/user/forced-user.h" <<'EOT' +FORCED_USER_BEGIN +#include "forced-child.h" +FORCED_USER_END +EOT +cat > "$base/project/user/forced-child.h" <<'EOT' +FORCED_USER_CHILD +EOT +( + cd "$base/project" + "$MCPU_CPP" --no-config -I user -include forced-user.h src/cwd-main.c \ + -o "$base/user.out" +) +grep '^FORCED_USER_BEGIN$' "$base/user.out" >/dev/null +grep '^FORCED_USER_CHILD$' "$base/user.out" >/dev/null +grep '^FORCED_USER_END$' "$base/user.out" >/dev/null + +# Search-chain provenance of a forced file is retained for #include_next. +cat > "$base/project/user/wrapped.h" <<'EOT' +FORCED_WRAPPER +#include_next <wrapped.h> +EOT +cat > "$base/project/sys/wrapped.h" <<'EOT' +FORCED_INCLUDE_NEXT +EOT +( + cd "$base/project" + "$MCPU_CPP" --no-config -I user -isystem sys -include wrapped.h \ + src/cwd-main.c -o "$base/next.out" +) +grep '^FORCED_WRAPPER$' "$base/next.out" >/dev/null +grep '^FORCED_INCLUDE_NEXT$' "$base/next.out" >/dev/null + +# -dD does not make normal output from -imacros visible; macro state still +# reaches the primary input. +cat > "$base/project/only-macros.h" <<'EOT' +IMACROS_DD_BODY_MUST_NOT_APPEAR +#define DD_VALUE 77 +EOT +cat > "$base/project/src/dd-main.c" <<'EOT' +DD_VALUE +EOT +( + cd "$base/project" + "$MCPU_CPP" --no-config -dD -imacros only-macros.h src/dd-main.c \ + -o "$base/dd.out" +) +! grep 'IMACROS_DD_BODY_MUST_NOT_APPEAR' "$base/dd.out" >/dev/null +! grep '^#define DD_VALUE 77$' "$base/dd.out" >/dev/null +grep '^77$' "$base/dd.out" >/dev/null + +# Forced files participate in physical dependency tracking. The primary input +# remains the first dependency even though forced files are preprocessed first. +( + cd "$base/project" + "$MCPU_CPP" --no-config -M -DCLI_VALUE=9 \ + -imacros defs1.h -imacros defs2.h -include a.h -include b.h src/main.c \ + > "$base/deps.out" +) +grep '^main\.o:' "$base/deps.out" >/dev/null +first_dep=`sed 's/^[^:]*: //' "$base/deps.out" | awk '{print $1}'` +test "$first_dep" = 'src/main.c' || { + echo 'primary input is not the first forced-file dependency' >&2 + exit 1 +} +for f in defs1.h imacros-child.h defs2.h a.h b.h; do + grep -E "(^|[[:space:]])(\./)?$f([[:space:]]|$)" "$base/deps.out" >/dev/null || { + echo "forced dependency missing: $f" >&2 + exit 1 + } +done + +# A forced file found through -isystem has system dependency class and is +# therefore omitted by -MM while the primary user source remains. +cat > "$base/project/sys/system-forced.h" <<'EOT' +SYSTEM_FORCED_BODY +EOT +( + cd "$base/project" + "$MCPU_CPP" --no-config -MM -isystem sys -include system-forced.h \ + src/cwd-main.c > "$base/mm.out" +) +grep -E '(^|[[:space:]])src/cwd-main\.c([[:space:]]|$)' "$base/mm.out" >/dev/null +! grep -F "$base/project/sys/system-forced.h" "$base/mm.out" >/dev/null + +# Public help advertises both implemented forced-file options. +"$MCPU_CPP" --help > "$base/help" +grep '^ -imacros FILE ' "$base/help" >/dev/null +grep '^ -include FILE ' "$base/help" >/dev/null + +# Both option families require an argument. +for opt in -include -imacros; do + if "$MCPU_CPP" --no-config "$opt" > /dev/null 2>"$base/missing-arg.err"; then + echo "$opt accepted a missing argument" >&2 + exit 1 + fi + grep -- "argument missing after '$opt'" "$base/missing-arg.err" >/dev/null +done + +# Missing forced files are preprocessing errors for both option families. +for opt in -include -imacros; do + if ( + cd "$base/project" + "$MCPU_CPP" --no-config "$opt" no-such-forced-file.h src/cwd-main.c \ + -o "$base/missing.out" 2>"$base/missing.err" + ); then + echo "$opt accepted a missing forced file" >&2 + exit 1 + fi + grep -- "$opt cannot find file 'no-such-forced-file.h'" "$base/missing.err" >/dev/null +done + +# The same forced-file pipeline also applies when the primary source is stdin. +printf 'DD_VALUE\n' | ( + cd "$base/project" + "$MCPU_CPP" --no-config -imacros only-macros.h - +) > "$base/stdin.out" +grep '^77$' "$base/stdin.out" >/dev/null diff --git a/tests/t0056-dependency-targets.sh b/tests/t0056-dependency-targets.sh new file mode 100755 index 0000000..ea5f4b9 --- /dev/null +++ b/tests/t0056-dependency-targets.sh @@ -0,0 +1,103 @@ +#!/bin/sh +set -eu + +base="${TMPDIR-/tmp}/mcpu-cpp-dependency-targets-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cd "$base" + +cat > main.c <<'EOT' +#include "local.h" +int main_value; +EOT + +cat > local.h <<'EOT' +#define LOCAL_VALUE 1 +EOT + +# The established default target remains make-quoted and uses the configured +# object suffix only when no explicit -MT/-MQ target is present. +"$MCPU_CPP" --no-config -M main.c > default.d +grep '^main\.o: main\.c \./local\.h$' default.d >/dev/null + +"$MCPU_CPP" --no-config --object-suffix .obj -M main.c > suffix.d +grep '^main\.obj: main\.c \./local\.h$' suffix.d >/dev/null + +# -MT replaces the default target and is emitted exactly as supplied. +"$MCPU_CPP" --no-config -M -MT build/main.o main.c > mt.d +grep '^build/main\.o: main\.c \./local\.h$' mt.d >/dev/null + +"$MCPU_CPP" --no-config -M -MTattached.o main.c > mt-attached.d +grep '^attached\.o: main\.c \./local\.h$' mt-attached.d >/dev/null + +"$MCPU_CPP" --no-config -M -MT 'raw one raw#two$' main.c > mt-raw.d +grep '^raw one raw#two\$: main\.c \./local\.h$' mt-raw.d >/dev/null + +# -MQ applies Make quoting to the target. +"$MCPU_CPP" --no-config -M -MQ '$(OBJDIR)/main.o' main.c > mq.d +grep '^\$\$(OBJDIR)/main\.o: main\.c \./local\.h$' mq.d >/dev/null + +"$MCPU_CPP" --no-config -M '-MQ$(OBJDIR)/attached.o' main.c > mq-attached.d +grep '^\$\$(OBJDIR)/attached\.o: main\.c \./local\.h$' mq-attached.d >/dev/null + +"$MCPU_CPP" --no-config -M -MQ 'dir name/#x$.o' main.c > mq-special.d +grep '^dir\\ name/\\#x\$\$\.o: main\.c \./local\.h$' mq-special.d >/dev/null + +"$MCPU_CPP" --no-config -M -MQ 'path\name.o' main.c > mq-backslash.d +grep -F 'path\name.o: main.c ./local.h' mq-backslash.d >/dev/null + +# Multiple targets produce one rule. As in GNU CPP, all -MT targets precede +# all -MQ targets; command-line order is preserved inside each class. +"$MCPU_CPP" --no-config -M \ + -MQ '$(DIR)/quoted1.o' \ + -MT raw1.o \ + -MQ 'quoted two.o' \ + -MT 'raw2.o raw3.o' \ + main.c > multiple.d +grep '^raw1\.o raw2\.o raw3\.o \$\$(DIR)/quoted1\.o quoted\\ two\.o: main\.c \./local\.h$' multiple.d >/dev/null + +# Explicit targets override the automatic target even when an object suffix was +# requested. +"$MCPU_CPP" --no-config --object-suffix .obj -M -MT explicit.target main.c > explicit.d +grep '^explicit\.target: main\.c \./local\.h$' explicit.d >/dev/null + +# -MT/-MQ work in side-effect dependency modes and with -MF. +"$MCPU_CPP" --no-config -MD -MF md.d -MT md-target.o main.c > md.i +grep '^md-target\.o: main\.c \./local\.h$' md.d >/dev/null +grep '^int main_value;$' md.i >/dev/null + +"$MCPU_CPP" --no-config -MMD -MF mmd.d -MQ '$(OUT)/mmd.o' main.c > mmd.i +grep '^\$\$(OUT)/mmd\.o: main\.c \./local\.h$' mmd.d >/dev/null +grep '^int main_value;$' mmd.i >/dev/null + +# Both options require an active dependency-generation mode. +if "$MCPU_CPP" --no-config -MT orphan.o main.c > orphan.out 2> orphan.err; then + echo "-MT without dependency generation unexpectedly succeeded" >&2 + exit 1 +fi +grep -- '-MT/-MQ require one of -M, -MM, -MD or -MMD' orphan.err >/dev/null + +if "$MCPU_CPP" --no-config -MQ orphan.o main.c > orphan-q.out 2> orphan-q.err; then + echo "-MQ without dependency generation unexpectedly succeeded" >&2 + exit 1 +fi +grep -- '-MT/-MQ require one of -M, -MM, -MD or -MMD' orphan-q.err >/dev/null + +# Missing arguments remain command-line errors. +if "$MCPU_CPP" --no-config -M -MT > missing-mt.out 2> missing-mt.err; then + echo "-MT without an argument unexpectedly succeeded" >&2 + exit 1 +fi +grep "argument missing after '-MT'" missing-mt.err >/dev/null + +if "$MCPU_CPP" --no-config -M -MQ > missing-mq.out 2> missing-mq.err; then + echo "-MQ without an argument unexpectedly succeeded" >&2 + exit 1 +fi +grep "argument missing after '-MQ'" missing-mq.err >/dev/null + +# Public help documents both options. +"$MCPU_CPP" --help > help.out +grep '^ -MT TARGET set unquoted make dependency target$' help.out >/dev/null +grep '^ -MQ TARGET set make-quoted dependency target$' help.out >/dev/null diff --git a/tests/t0057-missing-generated-dependencies.sh b/tests/t0057-missing-generated-dependencies.sh new file mode 100755 index 0000000..7ad5001 --- /dev/null +++ b/tests/t0057-missing-generated-dependencies.sh @@ -0,0 +1,166 @@ +#!/bin/sh +set -eu + +base="${TMPDIR-/tmp}/mcpu-cpp-missing-generated-deps-$$" +mkdir -p "$base/user" "$base/sys" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cd "$base" + +cat > main.c <<'EOT' +#include "generated-user.h" +#include <generated-system.h> +#define GENERATED_MACRO_HEADER "generated-macro.h" +#include GENERATED_MACRO_HEADER +EOT + +# Without -MG, the first unresolved include remains a hard error. +if "$MCPU_CPP" --no-config -M main.c > no-mg.out 2> no-mg.err; then + echo "missing include unexpectedly succeeded without -MG" >&2 + exit 1 +fi +grep "cannot find include file 'generated-user.h'" no-mg.err >/dev/null + +# -M -MG records unresolved operands exactly as written and does not diagnose +# them as missing files. Macro-expanded include operands use their expanded +# filename. +"$MCPU_CPP" --no-config -M -MG main.c > M.d 2> M.err +test ! -s M.err +grep '^main\.o:' M.d >/dev/null +for f in main.c generated-user.h generated-system.h generated-macro.h +do + grep -F "$f" M.d >/dev/null || { + echo "-M -MG omitted unresolved dependency: $f" >&2 + exit 1 + } +done + +# -MM retains unresolved quoted/user includes but filters unresolved angle +# includes as system-class dependencies. +"$MCPU_CPP" --no-config -MM -MG main.c > MM.d +grep -F 'generated-user.h' MM.d >/dev/null +grep -F 'generated-macro.h' MM.d >/dev/null +if grep -F 'generated-system.h' MM.d >/dev/null; then + echo "-MM -MG kept unresolved angle/system dependency" >&2 + exit 1 +fi + +# Missing dependencies reached from a real system header inherit system +# context, even when the nested directive uses quotes. User-header quoted +# descendants remain user dependencies. +cat > sys/system-parent.h <<'EOT' +#include "generated-from-system.h" +EOT +cat > user/user-parent.h <<'EOT' +#include "generated-from-user.h" +EOT +cat > nested.c <<'EOT' +#include <system-parent.h> +#include <user-parent.h> +EOT + +"$MCPU_CPP" --no-config -MM -MG -I user -isystem sys nested.c > nested-MM.d +grep -F 'user/user-parent.h' nested-MM.d >/dev/null +grep -F 'generated-from-user.h' nested-MM.d >/dev/null +if grep -F 'sys/system-parent.h' nested-MM.d >/dev/null || + grep -F 'generated-from-system.h' nested-MM.d >/dev/null; then + echo "-MM -MG kept system-context dependency" >&2 + exit 1 +fi + +"$MCPU_CPP" --no-config -M -MG -I user -isystem sys nested.c > nested-M.d +grep -F 'sys/system-parent.h' nested-M.d >/dev/null +grep -F 'generated-from-system.h' nested-M.d >/dev/null + +# Unresolved dependency identity is textual and separate from physical +# st_dev/st_ino identity. shadow.h exists in CWD, but <shadow.h> is not found +# through the angle search chain under --no-config; a later quoted ./shadow.h +# resolves that physical file. Both registry entries must survive under -M. +cat > shadow.h <<'EOT' +#define SHADOW_VALUE 1 +EOT +cat > identity.c <<'EOT' +#include <shadow.h> +#include "./shadow.h" +EOT +"$MCPU_CPP" --no-config -M -MG identity.c > identity-M.d +grep '^identity\.o: identity\.c shadow\.h \./\./shadow\.h$' identity-M.d >/dev/null || { + echo "unresolved and physical dependency identities were incorrectly merged" >&2 + cat identity-M.d >&2 + exit 1 +} + +# In -MM the unresolved angle entry is filtered, while the resolved quoted +# physical dependency remains. +"$MCPU_CPP" --no-config -MM -MG identity.c > identity-MM.d +grep -F '././shadow.h' identity-MM.d >/dev/null +case "$(cat identity-MM.d)" in + *' shadow.h '*) + echo "-MM kept the unresolved angle dependency" >&2 + exit 1 + ;; +esac + +# Repeated unresolved operands are deduplicated textually. Their first +# classification is retained, matching GNU CPP: angle-first stays system-only, +# while quote-first stays visible to -MM. +cat > first-system.c <<'EOT' +#include <same-missing.h> +#include "same-missing.h" +EOT +"$MCPU_CPP" --no-config -MM -MG first-system.c > first-system.d +if grep -F 'same-missing.h' first-system.d >/dev/null; then + echo "angle-first unresolved dependency lost its first classification" >&2 + exit 1 +fi + +cat > first-user.c <<'EOT' +#include "same-missing.h" +#include <same-missing.h> +EOT +"$MCPU_CPP" --no-config -MM -MG first-user.c > first-user.d +grep -F 'same-missing.h' first-user.d >/dev/null + +# Different unresolved spellings are distinct because no physical identity is +# available to canonicalize them. +cat > spelling.c <<'EOT' +#include "generated.h" +#include "./generated.h" +EOT +"$MCPU_CPP" --no-config -M -MG spelling.c > spelling.d +grep -F ' generated.h' spelling.d >/dev/null +grep -F ' ./generated.h' spelling.d >/dev/null + +# -MG also applies to missing command-line forced files. They enter the same +# unresolved registry as user dependencies and preserve the command-line +# operand; no synthetic path is prepended. +: > forced.c +"$MCPU_CPP" --no-config -M -MG \ + -imacros missing-macros.h -include missing-include.h forced.c > forced-M.d +grep -F 'missing-macros.h' forced-M.d >/dev/null +grep -F 'missing-include.h' forced-M.d >/dev/null + +"$MCPU_CPP" --no-config -MM -MG \ + -imacros missing-macros.h -include missing-include.h forced.c > forced-MM.d +grep -F 'missing-macros.h' forced-MM.d >/dev/null +grep -F 'missing-include.h' forced-MM.d >/dev/null + +# -MG is dependency-only: GNU permits it with -M/-MM, not side-effect -MD/-MMD +# and not without dependency generation. +for opts in '-MG' '-MD -MG' '-MMD -MG' +do + if "$MCPU_CPP" --no-config $opts forced.c > invalid.out 2> invalid.err; then + echo "$opts unexpectedly accepted" >&2 + exit 1 + fi + grep -- '-MG may only be used with -M or -MM' invalid.err >/dev/null +done + +# Explicit dependency targets continue to compose with -MG. +"$MCPU_CPP" --no-config -M -MG -MT generated-target.o main.c > target.d +grep '^generated-target\.o:' target.d >/dev/null +grep -F 'generated-user.h' target.d >/dev/null + +# Public help exposes the implemented option. +"$MCPU_CPP" --help > help.out +grep '^ -MG treat missing headers as generated dependencies$' help.out >/dev/null diff --git a/tests/t0058-variadic-macros.sh b/tests/t0058-variadic-macros.sh new file mode 100755 index 0000000..584e980 --- /dev/null +++ b/tests/t0058-variadic-macros.sh @@ -0,0 +1,135 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-variadic-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/main.c" <<'EOT' +#define A 7 +#define SUM(X, Y) X + Y +#define V(...) <__VA_ARGS__> +#define F(first, ...) first | __VA_ARGS__ +#define STRV(...) #__VA_ARGS__ +#define LEFT(...) pre ## __VA_ARGS__ +#define RIGHT(...) __VA_ARGS__ ## post +#define CALL(FN, ...) FN(__VA_ARGS__) +#define WRAP(...) V(__VA_ARGS__) +#define ID(X) X +#define РУССКИЙ(первый, ...) первый : __VA_ARGS__ + +V(alpha, beta, gamma) +V() +F(one, two, three) +F(one) +F(one,) +F(A, SUM(1, 2)) +STRV(A, b + c) +LEFT(fix) +LEFT() +RIGHT(fix) +RIGHT() +CALL(ID, A) +WRAP(A, SUM(3, 4)) +РУССКИЙ(один, два, три) +EOT + +"$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" + +grep -Fx '<alpha, beta, gamma>' "$base/out" >/dev/null +grep -Fx '<>' "$base/out" >/dev/null +grep -Fx 'one | two, three' "$base/out" >/dev/null +# Both an omitted variadic tail and an explicitly empty one expand to no tokens. +test "$(grep -Fxc 'one | ' "$base/out")" -eq 2 +grep -Fx '7 | 1 + 2' "$base/out" >/dev/null +grep -Fx '"A, b + c"' "$base/out" >/dev/null +grep -Fx 'prefix' "$base/out" >/dev/null +grep -Fx 'pre' "$base/out" >/dev/null +grep -Fx 'fixpost' "$base/out" >/dev/null +grep -Fx 'post' "$base/out" >/dev/null +grep -Fx '7' "$base/out" >/dev/null +grep -Fx '<7, 3 + 4>' "$base/out" >/dev/null +grep -Fx 'один : два, три' "$base/out" >/dev/null + +# The variable argument is preserved as such when macro definitions are dumped. +"$MCPU_CPP" --no-config -dM "$base/main.c" > "$base/dump" +grep -Fx '#define V(...) <__VA_ARGS__>' "$base/dump" >/dev/null +grep -Fx '#define F(first,...) first | __VA_ARGS__' "$base/dump" >/dev/null +grep -Fx '#define STRV(...) #__VA_ARGS__' "$base/dump" >/dev/null +grep -Fx '#define РУССКИЙ(первый,...) первый : __VA_ARGS__' "$base/dump" >/dev/null + + +# Variadic-ness is part of a macro definition. Repeating the same variadic +# definition is harmless, while changing a non-variadic definition into a +# variadic one is a real redefinition and therefore participates in -Werror. +cat > "$base/redefine-same.c" <<'EOT' +#define SAME(x, ...) x | __VA_ARGS__ +#define SAME(x, ...) x | __VA_ARGS__ +SAME(1, 2) +EOT +"$MCPU_CPP" --no-config "$base/redefine-same.c" \ + -o "$base/redefine-same.out" 2>"$base/redefine-same.err" +test ! -s "$base/redefine-same.err" +grep -Fx '1 | 2' "$base/redefine-same.out" >/dev/null + +cat > "$base/redefine-kind.c" <<'EOT' +#define CHANGE(x) x +#define CHANGE(x, ...) x | __VA_ARGS__ +EOT +if "$MCPU_CPP" --no-config -Werror "$base/redefine-kind.c" \ + -o "$base/redefine-kind.out" 2>"$base/redefine-kind.err"; then + echo 'variadic/non-variadic redefinition escaped -Werror' >&2 + exit 1 +fi +grep "error: macro 'CHANGE' redefined" "$base/redefine-kind.err" >/dev/null + +# A variadic macro may have fixed parameters, but those fixed parameters are +# still required. An empty spelling counts as an argument, just as it does +# for ordinary function-like macros. +cat > "$base/few.c" <<'EOT' +#define NEEDS_TWO(a, b, ...) a + b + __VA_ARGS__ +NEEDS_TWO(1) +EOT +if "$MCPU_CPP" --no-config "$base/few.c" -o "$base/few.out" 2>"$base/few.err"; then + echo 'variadic macro accepted too few fixed arguments' >&2 + exit 1 +fi +grep "macro 'NEEDS_TWO' used with too few arguments" "$base/few.err" >/dev/null + +# The old GNU named variadic form is deliberately outside the 0.0.46 +# contract. Only the C99-style ... + __VA_ARGS__ form is accepted. +cat > "$base/named.c" <<'EOT' +#define OLD(args...) args +EOT +if "$MCPU_CPP" --no-config "$base/named.c" -o "$base/named.out" 2>"$base/named.err"; then + echo 'GNU named variadic macro form unexpectedly accepted' >&2 + exit 1 +fi +grep 'badly punctuated parameter list in #define' "$base/named.err" >/dev/null + +cat > "$base/malformed.c" <<'EOT' +#define BAD(..., x) x +EOT +if "$MCPU_CPP" --no-config "$base/malformed.c" -o "$base/malformed.out" 2>"$base/malformed.err"; then + echo 'parameters after ... unexpectedly accepted' >&2 + exit 1 +fi +grep 'badly punctuated parameter list in #define' "$base/malformed.err" >/dev/null + +cat > "$base/reserved.c" <<'EOT' +#define BAD(__VA_ARGS__) __VA_ARGS__ +EOT +if "$MCPU_CPP" --no-config "$base/reserved.c" -o "$base/reserved.out" 2>"$base/reserved.err"; then + echo '__VA_ARGS__ unexpectedly accepted as an ordinary parameter name' >&2 + exit 1 +fi +grep "'__VA_ARGS__' cannot be used as a macro parameter name" "$base/reserved.err" >/dev/null + +# A replacement using #__VA_ARGS__ is valid only for a variadic macro. +cat > "$base/not-variadic.c" <<'EOT' +#define BAD(x) #__VA_ARGS__ +EOT +if "$MCPU_CPP" --no-config "$base/not-variadic.c" -o "$base/not-variadic.out" 2>"$base/not-variadic.err"; then + echo '#__VA_ARGS__ unexpectedly accepted in a non-variadic macro' >&2 + exit 1 +fi +grep "'#' operator should be followed by a macro argument name" "$base/not-variadic.err" >/dev/null diff --git a/tests/t0059-va-opt.sh b/tests/t0059-va-opt.sh new file mode 100755 index 0000000..052df6c --- /dev/null +++ b/tests/t0059-va-opt.sh @@ -0,0 +1,150 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-va-opt-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/main.c" <<'EOT' +#define EMPTY +#define X 123 +#define xy RESCANNED +#define HAS(...) __VA_OPT__(yes) +#define COMMA(format, ...) call(format __VA_OPT__(,) __VA_ARGS__) +#define STR(...) #__VA_OPT__(__VA_ARGS__) +#define STRFIX(a, ...) #__VA_OPT__(a __VA_ARGS__) +#define STRPASTE(a, ...) #__VA_OPT__(a ## z) +#define LEFT(...) pre ## __VA_OPT__(__VA_ARGS__) +#define RIGHT(...) __VA_OPT__(__VA_ARGS__) ## post +#define INNER(a, ...) __VA_OPT__(a ## y) +#define BALANCED(...) __VA_OPT__((a, (b, c))) +#define EMPTY_LEFT(...) x ## __VA_OPT__() +#define EMPTY_RIGHT(...) __VA_OPT__() ## y +#define TEXT(...) "__VA_OPT__(not syntax)" __VA_OPT__(ok) + +[HAS()] +[HAS(EMPTY)] +[HAS(token)] +COMMA("zero") +COMMA("two", X, 7) +STR() +STR(X) +STRFIX(X, y) +STRPASTE(X, y) +LEFT() +LEFT(X) +RIGHT() +RIGHT(X) +INNER(x, token) +BALANCED(token) +EMPTY_LEFT(token) +EMPTY_RIGHT(token) +TEXT() +TEXT(token) +EOT + +"$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" +sed '/^#/d; /^[[:space:]]*$/d; s/[[:space:]]//g' "$base/out" > "$base/norm" + +cat > "$base/expected" <<'EOT' +[] +[] +[yes] +call("zero") +call("two",123,7) +"" +"123" +"123y" +"Xz" +pre +pre123 +post +123post +RESCANNED +(a,(b,c)) +x +y +"__VA_OPT__(notsyntax)" +"__VA_OPT__(notsyntax)"ok +EOT + +diff -u "$base/expected" "$base/norm" + +# __VA_OPT__ remains visible in macro dumps as part of the replacement list. +"$MCPU_CPP" --no-config -dM "$base/main.c" > "$base/dump" +grep -Fx '#define HAS(...) __VA_OPT__(yes)' "$base/dump" >/dev/null +grep -Fx '#define COMMA(format,...) call(format __VA_OPT__(,) __VA_ARGS__)' "$base/dump" >/dev/null +grep -Fx '#define STR(...) #__VA_OPT__(__VA_ARGS__)' "$base/dump" >/dev/null + +# __VA_OPT__ is structural syntax, not a general identifier-like extension. +cat > "$base/nonvariadic.c" <<'EOT' +#define BAD(x) __VA_OPT__(x) +EOT +if "$MCPU_CPP" --no-config "$base/nonvariadic.c" -o "$base/nonvariadic.out" 2>"$base/nonvariadic.err"; then + echo '__VA_OPT__ unexpectedly accepted in a non-variadic macro' >&2 + exit 1 +fi +grep "'__VA_OPT__' may appear only in a variadic macro replacement list" "$base/nonvariadic.err" >/dev/null + +cat > "$base/object.c" <<'EOT' +#define BAD __VA_OPT__(x) +EOT +if "$MCPU_CPP" --no-config "$base/object.c" -o "$base/object.out" 2>"$base/object.err"; then + echo '__VA_OPT__ unexpectedly accepted in an object-like macro' >&2 + exit 1 +fi +grep "'__VA_OPT__' may appear only in a variadic macro replacement list" "$base/object.err" >/dev/null + +cat > "$base/nested.c" <<'EOT' +#define BAD(...) __VA_OPT__(a __VA_OPT__(b)) +EOT +if "$MCPU_CPP" --no-config "$base/nested.c" -o "$base/nested.out" 2>"$base/nested.err"; then + echo 'nested __VA_OPT__ unexpectedly accepted' >&2 + exit 1 +fi +grep "'__VA_OPT__' may not appear inside another '__VA_OPT__'" "$base/nested.err" >/dev/null + +cat > "$base/no-open.c" <<'EOT' +#define BAD(...) __VA_OPT__ x +EOT +if "$MCPU_CPP" --no-config "$base/no-open.c" -o "$base/no-open.out" 2>"$base/no-open.err"; then + echo '__VA_OPT__ without opening parenthesis unexpectedly accepted' >&2 + exit 1 +fi +grep "'__VA_OPT__' must be followed by '('" "$base/no-open.err" >/dev/null + +cat > "$base/unterminated.c" <<'EOT' +#define BAD(...) __VA_OPT__((x) +EOT +if "$MCPU_CPP" --no-config "$base/unterminated.c" -o "$base/unterminated.out" 2>"$base/unterminated.err"; then + echo 'unterminated __VA_OPT__ unexpectedly accepted' >&2 + exit 1 +fi +grep "unterminated '__VA_OPT__'" "$base/unterminated.err" >/dev/null + +cat > "$base/paste-first.c" <<'EOT' +#define BAD(...) __VA_OPT__(## x) +EOT +if "$MCPU_CPP" --no-config "$base/paste-first.c" -o "$base/paste-first.out" 2>"$base/paste-first.err"; then + echo 'leading ## inside __VA_OPT__ unexpectedly accepted' >&2 + exit 1 +fi +grep "'##' cannot appear at the beginning of '__VA_OPT__'" "$base/paste-first.err" >/dev/null + +cat > "$base/paste-last.c" <<'EOT' +#define BAD(...) __VA_OPT__(x ##) +EOT +if "$MCPU_CPP" --no-config "$base/paste-last.c" -o "$base/paste-last.out" 2>"$base/paste-last.err"; then + echo 'trailing ## inside __VA_OPT__ unexpectedly accepted' >&2 + exit 1 +fi +grep "'##' cannot appear at the end of '__VA_OPT__'" "$base/paste-last.err" >/dev/null + +# The historical GNU comma-swallow extension remains deliberately absent. +# __VA_OPT__ is the supported way to make a separator conditional. +cat > "$base/no-gnu-comma.c" <<'EOT' +#define OLD(format, ...) call(format, ## __VA_ARGS__) +OLD("x") +EOT +"$MCPU_CPP" --no-config "$base/no-gnu-comma.c" -o "$base/no-gnu-comma.out" 2>"$base/no-gnu-comma.err" +sed '/^#/d; /^[[:space:]]*$/d; s/[[:space:]]//g' "$base/no-gnu-comma.out" > "$base/no-gnu-comma.norm" +grep -Fx 'call("x",)' "$base/no-gnu-comma.norm" >/dev/null diff --git a/tests/t0060-macro-whitespace.sh b/tests/t0060-macro-whitespace.sh new file mode 100755 index 0000000..8f870ba --- /dev/null +++ b/tests/t0060-macro-whitespace.sh @@ -0,0 +1,72 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-macro-whitespace-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +cat > "$base/main.c" <<'EOT' +#define MULTI(fmt, ...) \ + do \ + { \ + output(fmt __VA_OPT__(,) __VA_ARGS__); \ + done(); \ + } \ + while( 0 ) + +#define TEXT "left right" +#define OPS + + - > < < +#define ID(x) x +#define V(...) __VA_OPT__(__VA_ARGS__) + +void +f( int x ) +{ + MULTI("x=%d", x); + MULTI("hello"); + ID(alpha + beta); + V(gamma + delta); + const char *s = TEXT; + OPS +} +EOT + +"$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" +sed '/^#/d; /^[[:space:]]*$/d' "$base/out" > "$base/norm" + +cat > "$base/expected" <<'EOT' +void +f( int x ) +{ + do { output("x=%d" , x); done(); } while( 0 ); + do { output("hello" ); done(); } while( 0 ); + alpha + beta; + gamma + delta; + const char *s = "left right"; + + + - > < < +} +EOT + +diff -u "$base/expected" "$base/norm" + +# Macro dumps use the same normalized replacement-list whitespace. +"$MCPU_CPP" --no-config -dM "$base/main.c" > "$base/dump" +grep -Fx '#define MULTI(fmt,...) do { output(fmt __VA_OPT__(,) __VA_ARGS__); done(); } while( 0 )' "$base/dump" >/dev/null +grep -Fx '#define TEXT "left right"' "$base/dump" >/dev/null +grep -Fx '#define OPS + + - > < <' "$base/dump" >/dev/null +grep -Fx '#define ID(x) x' "$base/dump" >/dev/null +grep -Fx '#define V(...) __VA_OPT__(__VA_ARGS__)' "$base/dump" >/dev/null + +# Ordinary source text that is not replacement-list formatting stays untouched. +cat > "$base/plain.c" <<'EOT' +int plain = 1; +#define ID(x) x +#define V(...) __VA_OPT__(__VA_ARGS__) +ID(a + b) +EOT +"$MCPU_CPP" --no-config "$base/plain.c" -o "$base/plain.out" +sed '/^#/d; /^[[:space:]]*$/d' "$base/plain.out" > "$base/plain.norm" +cat > "$base/plain.expected" <<'EOT' +int plain = 1; +a + b +EOT +diff -u "$base/plain.expected" "$base/plain.norm" diff --git a/tests/t0061-output-line-compaction.sh b/tests/t0061-output-line-compaction.sh new file mode 100755 index 0000000..e40b1ee --- /dev/null +++ b/tests/t0061-output-line-compaction.sh @@ -0,0 +1,131 @@ +#!/bin/sh +set -eu +base="${TMPDIR-/tmp}/mcpu-cpp-output-line-compaction-$$" +mkdir -p "$base" +trap 'rm -rf "$base"' EXIT HUP INT TERM + +# Seven invisible lines stay as seven ordinary newlines. The next visible +# source line is line 9, and no corrective marker is needed. +cat > "$base/gap7.c" <<'EOT' +int a; +// one +#define A 1 +#if 0 +hidden +#endif +/* six */ + +int b; +EOT +"$MCPU_CPP" --no-config "$base/gap7.c" -o "$base/gap7.out" +if grep -F "# 9 \"$base/gap7.c\"" "$base/gap7.out" >/dev/null; then + echo "unexpected line marker for seven-line invisible gap" >&2 + exit 1 +fi +gap7_count=`awk ' + $0 == "int a;" { inside = 1; next } + $0 == "int b;" { print count; exit } + inside { ++count } +' "$base/gap7.out"` +test "$gap7_count" -eq 7 + +# Eight invisible lines cross the GNU CPP threshold. They disappear from the +# byte stream and are represented by a line marker for the next visible line. +cat > "$base/gap8.c" <<'EOT' +int a; +// one +#define A 1 +#if 0 +hidden +#endif +/* six */ +// seven + +int b; +EOT +"$MCPU_CPP" --no-config "$base/gap8.c" -o "$base/gap8.out" +grep -F "# 10 \"$base/gap8.c\"" "$base/gap8.out" >/dev/null +awk ' + /^# 10 "/ { getline; if( $0 == "int b;" ) ok = 1 } + END { exit ok ? 0 : 1 } +' "$base/gap8.out" + +# A completely invisible header has no synthetic end-of-header marker. Only +# the structural enter/return markers survive. A long invisible gap in the +# parent is then represented by the parent's next visible source position. +cat > "$base/silent.h" <<'EOT' +#ifndef SILENT_H +#define SILENT_H 1 +#define H1 1 +#define H2 2 +#define H3 3 +#define H4 4 +#define H5 5 +#define H6 6 +#define H7 7 +#define H8 8 +#define H9 9 +#endif +EOT +cat > "$base/include.c" <<'EOT' +// leading comment +#include "silent.h" +// one +#define P1 1 +#if 0 +hidden +#endif +// six +// seven + +int visible; +EOT +"$MCPU_CPP" --no-config -I"$base" "$base/include.c" -o "$base/include.out" +grep -F "# 1 \"$base/silent.h\" 1" "$base/include.out" >/dev/null +grep -F "# 3 \"$base/include.c\" 2" "$base/include.out" >/dev/null +grep -F "# 11 \"$base/include.c\"" "$base/include.out" >/dev/null +if grep -E "^# (2|3|4|5|6|7|8|9|10|11|12) \"$base/silent.h\"" "$base/include.out" >/dev/null; then + echo "silent header acquired a synthetic progress marker" >&2 + exit 1 +fi +awk ' + /\/silent\.h" 1$/ { getline; if( $0 ~ /\/include\.c" 2$/ ) adjacent = 1 } + /^# 11 "/ { getline; if( $0 == "int visible;" ) visible = 1 } + END { exit adjacent && visible ? 0 : 1 } +' "$base/include.out" + +# The threshold is based on source position, not on why the lines are +# invisible. A long run of comments alone is compacted in exactly the same +# way as directives or inactive conditional text. +cat > "$base/comments.c" <<'EOT' +int before; +// 1 +// 2 +// 3 +// 4 +// 5 +// 6 +// 7 +// 8 +int after; +EOT +"$MCPU_CPP" --no-config "$base/comments.c" -o "$base/comments.out" +grep -F "# 10 \"$base/comments.c\"" "$base/comments.out" >/dev/null + +# __LINE__ observes source coordinates, not the compacted output layout. +cat > "$base/line.c" <<'EOT' +int a = __LINE__; +// 1 +// 2 +// 3 +// 4 +// 5 +// 6 +// 7 +// 8 +int b = __LINE__; +EOT +"$MCPU_CPP" --no-config "$base/line.c" -o "$base/line.out" +grep -Fx 'int a = 1;' "$base/line.out" >/dev/null +grep -Fx 'int b = 10;' "$base/line.out" >/dev/null +grep -F "# 10 \"$base/line.c\"" "$base/line.out" >/dev/null |
