diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1426512..66353fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ env: jobs: tests-ubuntu1804-gpp: + if: false # Disabled job strategy: matrix: os: [ubuntu-18.04] @@ -34,6 +35,7 @@ jobs: bash ./run.sh ${{ matrix.compiler }} v${{ matrix.version }} tests-ubuntu1804-clang: + if: false # Disabled job strategy: matrix: os: [ubuntu-18.04] @@ -79,6 +81,7 @@ jobs: bash ./run.sh ${{ matrix.compiler }} v${{ matrix.version }} tests-ubuntu1804-gpp-32: + if: false # Disabled job strategy: matrix: os: [ubuntu-18.04] @@ -95,6 +98,7 @@ jobs: bash ./run.sh ${{ matrix.compiler }} v${{ matrix.version }} 32 tests-ubuntu1804-clang-32: + if: false # Disabled job strategy: matrix: os: [ubuntu-18.04] @@ -147,7 +151,7 @@ jobs: matrix: os: [macos-11] compiler: [g++] - version: [9, 10] + version: [10, 11, 12] name: Use ${{ matrix.compiler }}-${{ matrix.version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} steps: @@ -200,10 +204,10 @@ jobs: cd tests bash ./run.sh ${{ matrix.compiler }} - tests-windows: + tests-windows-2019: strategy: matrix: - os: [windows-2019, windows-2022] + os: [windows-2019] compiler: [msvc, g++, clang++] name: Use ${{ matrix.compiler }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -213,3 +217,17 @@ jobs: run: | cd tests bash ./run.sh ${{ matrix.compiler }} + + tests-windows-2022: + strategy: + matrix: + os: [windows-2022] + compiler: [msvc, g++] + name: Use ${{ matrix.compiler }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Run tests + run: | + cd tests + bash ./run.sh ${{ matrix.compiler }} diff --git a/testlib.h b/testlib.h index 57b5781..8dab4c3 100644 --- a/testlib.h +++ b/testlib.h @@ -746,8 +746,8 @@ class random_t { /* Sets seed by given value. */ void setSeed(long long _seed) { - _seed = (_seed ^ multiplier) & mask; - seed = _seed; + seed = (unsigned long long) _seed; + seed = (seed ^ multiplier) & mask; } #ifndef __BORLANDC__ @@ -3121,7 +3121,6 @@ void InStream::init(std::string fileName, TMode mode) { } reset(); - skipBom(); } void InStream::init(std::FILE *f, TMode mode) { @@ -3137,7 +3136,6 @@ void InStream::init(std::FILE *f, TMode mode) { name = "stderr", stdfile = true; reset(f); - skipBom(); } void InStream::skipBom() { @@ -4628,6 +4626,7 @@ void registerTestlibCmd(int argc, char *argv[]) { inf.init(args[1], _input); ouf.init(args[2], _output); + ouf.skipBom(); ans.init(args[3], _answer); } diff --git a/tests/docker/clang-11/Dockerfile b/tests/docker/clang-11/Dockerfile new file mode 100644 index 0000000..04b5ba5 --- /dev/null +++ b/tests/docker/clang-11/Dockerfile @@ -0,0 +1,7 @@ +FROM silkeh/clang:11 +RUN apt-get update +RUN apt-get install -y git default-jre +COPY startup.sh / +WORKDIR / +RUN chmod +x /startup.sh +CMD ["/bin/bash", "/startup.sh"] diff --git a/tests/docker/clang-11/build.bat b/tests/docker/clang-11/build.bat new file mode 100644 index 0000000..77d2722 --- /dev/null +++ b/tests/docker/clang-11/build.bat @@ -0,0 +1,4 @@ +"C:\Program Files\Docker\Docker"\DockerCli.exe -SwitchLinuxEngine + +docker build . -t test-testlib-clang-11 + diff --git a/tests/docker/clang-11/run.bat b/tests/docker/clang-11/run.bat new file mode 100644 index 0000000..69f522e --- /dev/null +++ b/tests/docker/clang-11/run.bat @@ -0,0 +1,4 @@ +"C:\Program Files\Docker\Docker"\DockerCli.exe -SwitchLinuxEngine + +docker run -it test-testlib-clang-11 + diff --git a/tests/docker/clang-11/startup.sh b/tests/docker/clang-11/startup.sh new file mode 100644 index 0000000..bc3809f --- /dev/null +++ b/tests/docker/clang-11/startup.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e -o pipefail + +git clone https://github.com/MikeMirzayanov/testlib.git +cd testlib +git checkout dev-mikemirzayanov +cd tests +./run.sh v0 11 +cd / +rm -rf testlib diff --git a/tests/docker/clang-latest/Dockerfile b/tests/docker/clang-latest/Dockerfile new file mode 100644 index 0000000..0633e03 --- /dev/null +++ b/tests/docker/clang-latest/Dockerfile @@ -0,0 +1,7 @@ +FROM silkeh/clang:latest +RUN apt-get update +RUN apt-get install -y git default-jre valgrind +COPY startup.sh / +WORKDIR / +RUN chmod +x /startup.sh +CMD ["/bin/bash", "/startup.sh"] diff --git a/tests/docker/clang-latest/build.bat b/tests/docker/clang-latest/build.bat new file mode 100644 index 0000000..11735b4 --- /dev/null +++ b/tests/docker/clang-latest/build.bat @@ -0,0 +1,4 @@ +"C:\Program Files\Docker\Docker"\DockerCli.exe -SwitchLinuxEngine + +docker build . -t test-testlib-clang-latest + diff --git a/tests/docker/clang-latest/run.bat b/tests/docker/clang-latest/run.bat new file mode 100644 index 0000000..4267f38 --- /dev/null +++ b/tests/docker/clang-latest/run.bat @@ -0,0 +1,4 @@ +"C:\Program Files\Docker\Docker"\DockerCli.exe -SwitchLinuxEngine + +docker run -it test-testlib-clang-latest + diff --git a/tests/docker/clang-latest/startup.sh b/tests/docker/clang-latest/startup.sh new file mode 100644 index 0000000..e784d74 --- /dev/null +++ b/tests/docker/clang-latest/startup.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e -o pipefail + +git clone https://github.com/MikeMirzayanov/testlib.git +cd testlib +git checkout dev-mikemirzayanov +cd tests +./run.sh v0 23 +cd / +rm -rf testlib diff --git a/tests/docker/gcc-7/Dockerfile b/tests/docker/gcc-7/Dockerfile new file mode 100644 index 0000000..a6e2d85 --- /dev/null +++ b/tests/docker/gcc-7/Dockerfile @@ -0,0 +1,7 @@ +FROM gcc:7 +RUN apt-get update +RUN apt-get install -y git default-jre valgrind +COPY startup.sh / +WORKDIR / +RUN chmod +x /startup.sh +CMD ["/bin/bash", "/startup.sh"] diff --git a/tests/docker/gcc-7/build.bat b/tests/docker/gcc-7/build.bat new file mode 100644 index 0000000..947e59c --- /dev/null +++ b/tests/docker/gcc-7/build.bat @@ -0,0 +1,4 @@ +"C:\Program Files\Docker\Docker"\DockerCli.exe -SwitchLinuxEngine + +docker build . -t test-testlib-gcc-7 + diff --git a/tests/docker/gcc-7/run.bat b/tests/docker/gcc-7/run.bat new file mode 100644 index 0000000..7e4c4f1 --- /dev/null +++ b/tests/docker/gcc-7/run.bat @@ -0,0 +1,4 @@ +"C:\Program Files\Docker\Docker"\DockerCli.exe -SwitchLinuxEngine + +docker run -it test-testlib-gcc-7 + diff --git a/tests/docker/gcc-7/startup.sh b/tests/docker/gcc-7/startup.sh new file mode 100644 index 0000000..7db5d66 --- /dev/null +++ b/tests/docker/gcc-7/startup.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e -o pipefail + +git clone https://github.com/MikeMirzayanov/testlib.git +cd testlib +git checkout dev-mikemirzayanov +cd tests +./run.sh g++ v0 11 +cd / +rm -rf testlib diff --git a/tests/docker/gcc-latest/Dockerfile b/tests/docker/gcc-latest/Dockerfile new file mode 100644 index 0000000..9eb8320 --- /dev/null +++ b/tests/docker/gcc-latest/Dockerfile @@ -0,0 +1,7 @@ +FROM gcc:latest +RUN apt-get update +RUN apt-get install -y git default-jre valgrind +COPY startup.sh / +WORKDIR / +RUN chmod +x /startup.sh +CMD ["/bin/bash", "/startup.sh"] diff --git a/tests/docker/gcc-latest/build.bat b/tests/docker/gcc-latest/build.bat new file mode 100644 index 0000000..f5a6ac8 --- /dev/null +++ b/tests/docker/gcc-latest/build.bat @@ -0,0 +1,4 @@ +"C:\Program Files\Docker\Docker"\DockerCli.exe -SwitchLinuxEngine + +docker build . -t test-testlib-gcc-latest + diff --git a/tests/docker/gcc-latest/run.bat b/tests/docker/gcc-latest/run.bat new file mode 100644 index 0000000..852f80f --- /dev/null +++ b/tests/docker/gcc-latest/run.bat @@ -0,0 +1,4 @@ +"C:\Program Files\Docker\Docker"\DockerCli.exe -SwitchLinuxEngine + +docker run -it test-testlib-gcc-latest + diff --git a/tests/docker/gcc-latest/startup.sh b/tests/docker/gcc-latest/startup.sh new file mode 100644 index 0000000..d52e85c --- /dev/null +++ b/tests/docker/gcc-latest/startup.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e -o pipefail + +git clone https://github.com/MikeMirzayanov/testlib.git +cd testlib +git checkout dev-mikemirzayanov +cd tests +./run.sh g++ v0 23 +cd / +rm -rf testlib diff --git a/tests/file-runner.py b/tests/file-runner.py new file mode 100644 index 0000000..e768b71 --- /dev/null +++ b/tests/file-runner.py @@ -0,0 +1,9 @@ +import subprocess +import sys + +if len(sys.argv) > 1: + file_path = sys.argv[1] + subprocess.run([file_path]) +else: + print("Use python runner.py ") + sys.exit(1) diff --git a/tests/lib/msvc-2022-include.7z b/tests/lib/msvc-2022-include.7z deleted file mode 100644 index cab1e9a..0000000 Binary files a/tests/lib/msvc-2022-include.7z and /dev/null differ diff --git a/tests/lib/testlib.h b/tests/lib/testlib.h index 369aa61..8dab4c3 100644 --- a/tests/lib/testlib.h +++ b/tests/lib/testlib.h @@ -1,16 +1,16 @@ -/* - * It is strictly recommended to include "testlib.h" before any other include +/* + * It is strictly recommended to include "testlib.h" before any other include * in your code. In this case testlib overrides compiler specific "random()". * - * If you can't compile your code and compiler outputs something about - * ambiguous call of "random_shuffle", "rand" or "srand" it means that + * If you can't compile your code and compiler outputs something about + * ambiguous call of "random_shuffle", "rand" or "srand" it means that * you shouldn't use them. Use "shuffle", and "rnd.next()" instead of them - * because these calls produce stable result for any C++ compiler. Read + * because these calls produce stable result for any C++ compiler. Read * sample generator sources for clarification. * * Please read the documentation for class "random_t" and use "rnd" instance in * generators. Probably, these sample calls will be useful for you: - * rnd.next(); rnd.next(100); rnd.next(1, 2); + * rnd.next(); rnd.next(100); rnd.next(1, 2); * rnd.next(3.14); rnd.next("[a-z]{1,100}"). * * Also read about wnext() to generate off-center random distribution. @@ -22,18 +22,18 @@ #define _TESTLIB_H_ /* - * Copyright (c) 2005-2020 + * Copyright (c) 2005-2022 */ -#define VERSION "0.9.27-SNAPSHOT" +#define VERSION "0.9.40-SNAPSHOT" -/* +/* * Mike Mirzayanov * * This material is provided "as is", with absolutely no warranty expressed * or implied. Any use is at your own risk. * - * Permission to use or copy this software for any purpose is hereby granted + * Permission to use or copy this software for any purpose is hereby granted * without fee, provided the above notices are retained on all copies. * Permission to modify the code and to distribute modified code is granted, * provided the above notices are retained, and a notice that the code was @@ -47,15 +47,15 @@ * check.exe [ [-appes]], * If result file is specified it will contain results. * - * Validator, using testlib running format: + * Validator, using testlib running format: * validator.exe < input.txt, * It will return non-zero exit code and writes message to standard output. * - * Generator, using testlib running format: + * Generator, using testlib running format: * gen.exe [parameter-1] [parameter-2] [... paramerter-n] * You can write generated test(s) into standard output or into the file(s). * - * Interactor, using testlib running format: + * Interactor, using testlib running format: * interactor.exe [ [ [-appes]]], * Reads test from inf (mapped to args[1]), writes result to tout (mapped to argv[2], * can be judged by checker later), reads program output from ouf (mapped to stdin), @@ -63,6 +63,16 @@ */ const char *latestFeatures[] = { + "Supported '--testMarkupFileName fn' and '--testCase tc/--testCaseFileName fn' for validators", + "Added opt defaults via opt(key/index, default_val); check unused opts when using has_opt or default opt (turn off this check with suppressEnsureNoUnusedOpt()).", + "For checker added --group and --testset command line params (like for validator), use checker.group() or checker.testset() to get values", + "Added quitpi(points_info, message) function to return with _points exit code 7 and given points_info", + "rnd.partition(size, sum[, min_part=1]) returns random (unsorted) partition which is a representation of the given `sum` as a sum of `size` positive integers (or >=min_part if specified)", + "rnd.distinct(size, n) and rnd.distinct(size, from, to)", + "opt(\"some_missing_key\") returns false now", + "has_opt(key)", + "Abort validator on validator.testset()/validator.group() if registered without using command line", + "Print integer range violations in a human readable way like `violates the range [1, 10^9]`", "Opts supported: use them like n = opt(\"n\"), in a command line you can use an exponential notation", "Reformatted", "Use setTestCase(i) or unsetTestCase() to support test cases (you can use it in any type of program: generator, interactor, validator or checker)", @@ -170,8 +180,14 @@ const char *latestFeatures[] = { #include #include #include +#include +#include + +#ifdef TESTLIB_THROW_EXIT_EXCEPTION_INSTEAD_OF_EXIT +# include +#endif -#if (_WIN32 || __WIN32__ || _WIN64 || __WIN64__ || __CYGWIN__) +#if (_WIN32 || __WIN32__ || __WIN32 || _WIN64 || __WIN64__ || __WIN64 || WINNT || __WINNT || __WINNT__ || __CYGWIN__) # if !defined(_MSC_VER) || _MSC_VER > 1400 # define NOMINMAX 1 # include @@ -309,14 +325,14 @@ static int __testlib_format_buffer_usage_count = 0; __testlib_format_buffer_usage_count--; \ const long long __TESTLIB_LONGLONG_MAX = 9223372036854775807LL; +const int __TESTLIB_MAX_TEST_CASE = 1073741823; + +int __testlib_exitCode; bool __testlib_hasTestCase; int __testlib_testCase = -1; -void setTestCase(int testCase) { - __testlib_hasTestCase = true; - __testlib_testCase = testCase; -} +void setTestCase(int testCase); void unsetTestCase() { __testlib_hasTestCase = false; @@ -340,6 +356,18 @@ static inline T __testlib_max(const T &a, const T &b) { return a > b ? a : b; } +template +static inline T __testlib_crop(T value, T a, T b) { + return __testlib_min(__testlib_max(value, a), --b); +} + +static inline double __testlib_crop(double value, double a, double b) { + value = __testlib_min(__testlib_max(value, a), b); + if (value >= b) + value = std::nexttoward(b, a); + return value; +} + static bool __testlib_prelimIsNaN(double r) { volatile double ra = r; #ifndef __BORLANDC__ @@ -352,7 +380,10 @@ static bool __testlib_prelimIsNaN(double r) { static std::string removeDoubleTrailingZeroes(std::string value) { while (!value.empty() && value[value.length() - 1] == '0' && value.find('.') != std::string::npos) value = value.substr(0, value.length() - 1); - return value + '0'; + if (!value.empty() && value[value.length() - 1] == '.') + return value + '0'; + else + return value; } #ifdef __GNUC__ @@ -402,6 +433,7 @@ static bool __testlib_isInfinite(double r) { __attribute__((const)) #endif inline bool doubleCompare(double expected, double result, double MAX_DOUBLE_ERROR) { + MAX_DOUBLE_ERROR += 1E-15; if (__testlib_isNaN(expected)) { return __testlib_isNaN(result); } else if (__testlib_isInfinite(expected)) { @@ -412,14 +444,14 @@ inline bool doubleCompare(double expected, double result, double MAX_DOUBLE_ERRO } } else if (__testlib_isNaN(result) || __testlib_isInfinite(result)) { return false; - } else if (__testlib_abs(result - expected) <= MAX_DOUBLE_ERROR + 1E-15) { + } else if (__testlib_abs(result - expected) <= MAX_DOUBLE_ERROR) { return true; } else { double minv = __testlib_min(expected * (1.0 - MAX_DOUBLE_ERROR), expected * (1.0 + MAX_DOUBLE_ERROR)); double maxv = __testlib_max(expected * (1.0 - MAX_DOUBLE_ERROR), expected * (1.0 + MAX_DOUBLE_ERROR)); - return result + 1E-15 >= minv && result <= maxv + 1E-15; + return result >= minv && result <= maxv; } } @@ -436,32 +468,154 @@ inline double doubleDelta(double expected, double result) { return absolute; } -#if !defined(_MSC_VER) || _MSC_VER < 1900 -#ifndef _fileno -#define _fileno(_stream) ((_stream)->_file) -#endif +/** It does nothing on non-windows and files differ from stdin/stdout/stderr. */ +static void __testlib_set_binary(std::FILE *file) { + if (NULL != file) { +#ifdef ON_WINDOWS +# ifdef _O_BINARY + if (stdin == file) +# ifdef STDIN_FILENO + return void(_setmode(STDIN_FILENO, _O_BINARY)); +# else + return void(_setmode(_fileno(stdin), _O_BINARY)); +# endif + if (stdout == file) +# ifdef STDOUT_FILENO + return void(_setmode(STDOUT_FILENO, _O_BINARY)); +# else + return void(_setmode(_fileno(stdout), _O_BINARY)); +# endif + if (stderr == file) +# ifdef STDERR_FILENO + return void(_setmode(STDERR_FILENO, _O_BINARY)); +# else + return void(_setmode(_fileno(stderr), _O_BINARY)); +# endif +# elif O_BINARY + if (stdin == file) +# ifdef STDIN_FILENO + return void(setmode(STDIN_FILENO, O_BINARY)); +# else + return void(setmode(fileno(stdin), O_BINARY)); +# endif + if (stdout == file) +# ifdef STDOUT_FILENO + return void(setmode(STDOUT_FILENO, O_BINARY)); +# else + return void(setmode(fileno(stdout), O_BINARY)); +# endif + if (stderr == file) +# ifdef STDERR_FILENO + return void(setmode(STDERR_FILENO, O_BINARY)); +# else + return void(setmode(fileno(stderr), O_BINARY)); +# endif +# endif #endif + } +} -#ifndef O_BINARY -static void __testlib_set_binary( -#ifdef __GNUC__ - __attribute__((unused)) -#endif - std::FILE* file -) +#if __cplusplus > 199711L || defined(_MSC_VER) +template +static std::string vtos(const T &t, std::true_type) { + if (t == 0) + return "0"; + else { + T n(t); + bool negative = n < 0; + std::string s; + while (n != 0) { + T digit = n % 10; + if (digit < 0) + digit = -digit; + s += char('0' + digit); + n /= 10; + } + std::reverse(s.begin(), s.end()); + return negative ? "-" + s : s; + } +} + +template +static std::string vtos(const T &t, std::false_type) { + std::string s; + static std::stringstream ss; + ss.str(std::string()); + ss.clear(); + ss << t; + ss >> s; + return s; +} + +template +static std::string vtos(const T &t) { + return vtos(t, std::is_integral()); +} + +/* signed case. */ +template +static std::string toHumanReadableString(const T &n, std::false_type) { + if (n == 0) + return vtos(n); + int trailingZeroCount = 0; + T n_ = n; + while (n_ % 10 == 0) + n_ /= 10, trailingZeroCount++; + if (trailingZeroCount >= 7) { + if (n_ == 1) + return "10^" + vtos(trailingZeroCount); + else if (n_ == -1) + return "-10^" + vtos(trailingZeroCount); + else + return vtos(n_) + "*10^" + vtos(trailingZeroCount); + } else + return vtos(n); +} + +/* unsigned case. */ +template +static std::string toHumanReadableString(const T &n, std::true_type) { + if (n == 0) + return vtos(n); + int trailingZeroCount = 0; + T n_ = n; + while (n_ % 10 == 0) + n_ /= 10, trailingZeroCount++; + if (trailingZeroCount >= 7) { + if (n_ == 1) + return "10^" + vtos(trailingZeroCount); + else + return vtos(n_) + "*10^" + vtos(trailingZeroCount); + } else + return vtos(n); +} + +template +static std::string toHumanReadableString(const T &n) { + return toHumanReadableString(n, std::is_unsigned()); +} #else -static void __testlib_set_binary(std::FILE *file) -#endif +template +static std::string vtos(const T& t) { -#ifdef O_BINARY - if (NULL != file) { -#ifndef __BORLANDC__ - _setmode(_fileno(file), O_BINARY); -#else - setmode(fileno(file), O_BINARY); -#endif - } + std::string s; + static std::stringstream ss; + ss.str(std::string()); + ss.clear(); + ss << t; + ss >> s; + return s; +} + +template +static std::string toHumanReadableString(const T &n) { + return vtos(n); +} #endif + +template +static std::string toString(const T &t) { + return vtos(t); } #if __cplusplus > 199711L || defined(_MSC_VER) @@ -472,17 +626,17 @@ void prepareOpts(int argc, char* argv[]); /* * Very simple regex-like pattern. * It used for two purposes: validation and generation. - * + * * For example, pattern("[a-z]{1,5}").next(rnd) will return - * random string from lowercase latin letters with length - * from 1 to 5. It is easier to call rnd.next("[a-z]{1,5}") - * for the same effect. - * + * random string from lowercase latin letters with length + * from 1 to 5. It is easier to call rnd.next("[a-z]{1,5}") + * for the same effect. + * * Another samples: * "mike|john" will generate (match) "mike" or "john"; * "-?[1-9][0-9]{0,3}" will generate (match) non-zero integers from -9999 to 9999; * "id-([ac]|b{2})" will generate (match) "id-a", "id-bb", "id-c"; - * "[^0-9]*" will match sequences (empty or non-empty) without digits, you can't + * "[^0-9]*" will match sequences (empty or non-empty) without digits, you can't * use it for generations. * * You can't use pattern for generation if it contains meta-symbol '*'. Also it @@ -490,15 +644,19 @@ void prepareOpts(int argc, char* argv[]); * * For matching very simple greedy algorithm is used. For example, pattern * "[0-9]?1" will not match "1", because of greedy nature of matching. - * Alternations (meta-symbols "|") are processed with brute-force algorithm, so + * Alternations (meta-symbols "|") are processed with brute-force algorithm, so * do not use many alternations in one expression. * * If you want to use one expression many times it is better to compile it into - * a single pattern like "pattern p("[a-z]+")". Later you can use + * a single pattern like "pattern p("[a-z]+")". Later you can use * "p.matches(std::string s)" or "p.next(random_t& rd)" to check matching or generate * new string by pattern. - * + * * Simpler way to read token and check it for pattern matching is "inf.readToken("[a-z]+")". + * + * All spaces are ignored in regex, unless escaped with \. For example, ouf.readLine("NO SOLUTION") + * will expect "NOSOLUTION", the correct call should be ouf.readLine("NO\\ SOLUTION") or + * ouf.readLine(R"(NO\ SOLUTION)") if you prefer raw string literals from C++11. */ class random_t; @@ -526,9 +684,9 @@ class pattern { int to; }; -/* +/* * Use random_t instances to generate random values. It is preferred - * way to use randoms instead of rand() function or self-written + * way to use randoms instead of rand() function or self-written * randoms. * * Testlib defines global variable "rnd" of random_t class. @@ -588,8 +746,8 @@ class random_t { /* Sets seed by given value. */ void setSeed(long long _seed) { - _seed = (_seed ^ multiplier) & mask; - seed = _seed; + seed = (unsigned long long) _seed; + seed = (seed ^ multiplier) & mask; } #ifndef __BORLANDC__ @@ -706,25 +864,27 @@ class random_t { double next() { long long left = ((long long) (nextBits(26)) << 27); long long right = nextBits(27); - return (double) (left + right) / (double) (1LL << 53); + return __testlib_crop((double) (left + right) / (double) (1LL << 53), 0.0, 1.0); } /* Random double value in range [0, n). */ double next(double n) { - return n * next(); + if (n <= 0.0) + __testlib_fail("random_t::next(double): n should be positive"); + return __testlib_crop(n * next(), 0.0, n); } /* Random double value in range [from, to). */ double next(double from, double to) { - if (from > to) - __testlib_fail("random_t::next(double from, double to): from can't not exceed to"); + if (from >= to) + __testlib_fail("random_t::next(double from, double to): from should be strictly less than to"); return next(to - from) + from; } /* Returns random element from container. */ template typename Container::value_type any(const Container &c) { - size_t size = c.size(); + int size = int(c.size()); if (size <= 0) __testlib_fail("random_t::any(const Container& c): c.size() must be positive"); return *(c.begin() + next(size)); @@ -748,7 +908,7 @@ class random_t { return next(ptrn); } - /* + /* * Weighted next. If type == 0 than it is usual "next()". * * If type = 1, than it returns "max(next(), next())" @@ -778,7 +938,7 @@ class random_t { else p = 1 - std::pow(next() + 0.0, 1.0 / (-type + 1)); - return int(n * p); + return __testlib_crop((int) (double(n) * p), 0, n); } } @@ -803,37 +963,13 @@ class random_t { if (type > 0) p = std::pow(next() + 0.0, 1.0 / (type + 1)); else - p = std::pow(next() + 0.0, -type + 1); - - return __testlib_min(__testlib_max((long long) (double(n) * p), 0LL), n - 1LL); - } - } - - /* See wnext(int, int). It uses the same algorithms. */ - double wnext(int type) { - if (abs(type) < random_t::lim) { - double result = next(); - - for (int i = 0; i < +type; i++) - result = __testlib_max(result, next()); - - for (int i = 0; i < -type; i++) - result = __testlib_min(result, next()); - - return result; - } else { - double p; - - if (type > 0) - p = std::pow(next() + 0.0, 1.0 / (type + 1)); - else - p = std::pow(next() + 0.0, -type + 1); + p = 1 - std::pow(next() + 0.0, 1.0 / (-type + 1)); - return p; + return __testlib_crop((long long) (double(n) * p), 0LL, n); } } - /* See wnext(int, int). It uses the same algorithms. */ + /* Returns value in [0, n). See wnext(int, int). It uses the same algorithms. */ double wnext(double n, int type) { if (n <= 0) __testlib_fail("random_t::wnext(double n, int type): n must be positive"); @@ -854,12 +990,17 @@ class random_t { if (type > 0) p = std::pow(next() + 0.0, 1.0 / (type + 1)); else - p = std::pow(next() + 0.0, -type + 1); + p = 1 - std::pow(next() + 0.0, 1.0 / (-type + 1)); - return n * p; + return __testlib_crop(n * p, 0.0, n); } } + /* Returns value in [0, 1). See wnext(int, int). It uses the same algorithms. */ + double wnext(int type) { + return wnext(1.0, type); + } + /* See wnext(int, int). It uses the same algorithms. */ unsigned int wnext(unsigned int n, int type) { if (n >= INT_MAX) @@ -933,8 +1074,8 @@ class random_t { /* Returns weighted random double value in range [from, to). */ double wnext(double from, double to, int type) { - if (from > to) - __testlib_fail("random_t::wnext(double from, double to, int type): from can't not exceed to"); + if (from >= to) + __testlib_fail("random_t::wnext(double from, double to, int type): from should be strictly less than to"); return wnext(to - from, type) + from; } @@ -957,23 +1098,132 @@ class random_t { return *(begin + wnext(size, type)); } + /* Returns random permutation of the given size (values are between `first` and `first`+size-1)*/ template std::vector perm(T size, E first) { - if (size <= 0) - __testlib_fail("random_t::perm(T size, E first = 0): size must be positive"); + if (size < 0) + __testlib_fail("random_t::perm(T size, E first = 0): size must non-negative"); + else if (size == 0) + return std::vector(); std::vector p(size); + E current = first; for (T i = 0; i < size; i++) - p[i] = first + i; + p[i] = current++; if (size > 1) for (T i = 1; i < size; i++) std::swap(p[i], p[next(i + 1)]); return p; } + /* Returns random permutation of the given size (values are between 0 and size-1)*/ template std::vector perm(T size) { return perm(size, T(0)); } + + /* Returns `size` unordered (unsorted) distinct numbers between `from` and `to`. */ + template + std::vector distinct(int size, T from, T to) { + std::vector result; + if (size == 0) + return result; + + if (from > to) + __testlib_fail("random_t::distinct expected from <= to"); + + if (size < 0) + __testlib_fail("random_t::distinct expected size >= 0"); + + uint64_t n = to - from + 1; + if (uint64_t(size) > n) + __testlib_fail("random_t::distinct expected size <= to - from + 1"); + + double expected = 0.0; + for (int i = 1; i <= size; i++) + expected += double(n) / double(n - i + 1); + + if (expected < double(n)) { + std::set vals; + while (int(vals.size()) < size) { + T x = T(next(from, to)); + if (vals.insert(x).second) + result.push_back(x); + } + } else { + if (n > 1000000000) + __testlib_fail("random_t::distinct here expected to - from + 1 <= 1000000000"); + std::vector p(perm(int(n), from)); + result.insert(result.end(), p.begin(), p.begin() + size); + } + + return result; + } + + /* Returns `size` unordered (unsorted) distinct numbers between `0` and `upper`-1. */ + template + std::vector distinct(int size, T upper) { + if (size < 0) + __testlib_fail("random_t::distinct expected size >= 0"); + if (size == 0) + return std::vector(); + + if (upper <= 0) + __testlib_fail("random_t::distinct expected upper > 0"); + if (size > upper) + __testlib_fail("random_t::distinct expected size <= upper"); + + return distinct(size, T(0), upper - 1); + } + + /* Returns random (unsorted) partition which is a representation of sum as a sum of integers not less than min_part. */ + template + std::vector partition(int size, T sum, T min_part) { + if (size < 0) + __testlib_fail("random_t::partition: size < 0"); + if (size == 0 && sum != 0) + __testlib_fail("random_t::partition: size == 0 && sum != 0"); + if (min_part * size > sum) + __testlib_fail("random_t::partition: min_part * size > sum"); + if (size == 0 && sum == 0) + return std::vector(); + + T sum_ = sum; + sum -= min_part * size; + + std::vector septums(size); + std::vector d = distinct(size - 1, T(1), T(sum + size - 1)); + for (int i = 0; i + 1 < size; i++) + septums[i + 1] = d[i]; + sort(septums.begin(), septums.end()); + + std::vector result(size); + for (int i = 0; i + 1 < size; i++) + result[i] = septums[i + 1] - septums[i] - 1; + result[size - 1] = sum + size - 1 - septums.back(); + + for (std::size_t i = 0; i < result.size(); i++) + result[i] += min_part; + + T result_sum = 0; + for (std::size_t i = 0; i < result.size(); i++) + result_sum += result[i]; + if (result_sum != sum_) + __testlib_fail("random_t::partition: partition sum is expected to be the given sum"); + + if (*std::min_element(result.begin(), result.end()) < min_part) + __testlib_fail("random_t::partition: partition min is expected to be no less than the given min_part"); + + if (int(result.size()) != size || result.size() != (size_t) size) + __testlib_fail("random_t::partition: partition size is expected to be equal to the given size"); + + return result; + } + + /* Returns random (unsorted) partition which is a representation of sum as a sum of positive integers. */ + template + std::vector partition(int size, T sum) { + return partition(size, sum, T(1)); + } }; const int random_t::lim = 25; @@ -1156,6 +1406,8 @@ static std::vector __pattern_scanCharSet(const std::string &s, size_t &pos if (__pattern_isCommandChar(s, pos, '[')) { pos++; bool negative = __pattern_isCommandChar(s, pos, '^'); + if (negative) + pos++; char prev = 0; @@ -1320,7 +1572,7 @@ enum TResult { }; enum TTestlibMode { - _unknown, _checker, _validator, _generator, _interactor + _unknown, _checker, _validator, _generator, _interactor, _scorer }; #define _pc(exitCode) (TResult(_partially + (exitCode))) @@ -1352,6 +1604,10 @@ const std::string outcomes[] = { class InputStreamReader { public: + virtual void setTestCase(int testCase) = 0; + + virtual std::vector getReadChars() = 0; + virtual int curChar() = 0; virtual int nextChar() = 0; @@ -1385,6 +1641,14 @@ class StringInputStreamReader : public InputStreamReader { // No operations. } + void setTestCase(int) { + __testlib_fail("setTestCase not implemented in StringInputStreamReader"); + } + + std::vector getReadChars() { + __testlib_fail("getReadChars not implemented in StringInputStreamReader"); + } + int curChar() { if (pos >= s.length()) return EOFC; @@ -1406,7 +1670,7 @@ class StringInputStreamReader : public InputStreamReader { void unreadChar(int c) { if (pos == 0) - __testlib_fail("FileFileInputStreamReader::unreadChar(int): pos == 0."); + __testlib_fail("StringInputStreamReader::unreadChar(int): pos == 0."); pos--; if (pos < s.length()) s[pos] = char(c); @@ -1435,6 +1699,8 @@ class FileInputStreamReader : public InputStreamReader { std::string name; int line; std::vector undoChars; + std::vector readChars; + std::vector undoReadChars; inline int postprocessGetc(int getcResult) { if (getcResult != EOF) @@ -1445,19 +1711,29 @@ class FileInputStreamReader : public InputStreamReader { int getc(FILE *file) { int c; - if (undoChars.empty()) - c = ::getc(file); - else { + int rc; + + if (undoChars.empty()) { + c = rc = ::getc(file); + } else { c = undoChars.back(); undoChars.pop_back(); + rc = undoReadChars.back(); + undoReadChars.pop_back(); } if (c == LF) line++; + + readChars.push_back(rc); return c; } int ungetc(int c/*, FILE* file*/) { + if (!readChars.empty()) { + undoReadChars.push_back(readChars.back()); + readChars.pop_back(); + } if (c == LF) line--; undoChars.push_back(c); @@ -1469,6 +1745,16 @@ class FileInputStreamReader : public InputStreamReader { // No operations. } + void setTestCase(int testCase) { + if (testCase < 0 || testCase > __TESTLIB_MAX_TEST_CASE) + __testlib_fail(format("testCase expected fit in [1,%d], but %d doesn't", __TESTLIB_MAX_TEST_CASE, testCase)); + readChars.push_back(testCase + 256); + } + + std::vector getReadChars() { + return readChars; + } + int curChar() { if (feof(file)) return EOFC; @@ -1528,14 +1814,14 @@ class BufferedFileInputStreamReader : public InputStreamReader { static const size_t MAX_UNREAD_COUNT; std::FILE *file; + std::string name; + int line; + char *buffer; bool *isEof; int bufferPos; size_t bufferSize; - std::string name; - int line; - bool refill() { if (NULL == file) __testlib_fail("BufferedFileInputStreamReader: file == NULL (" + getName() + ")"); @@ -1587,6 +1873,14 @@ class BufferedFileInputStreamReader : public InputStreamReader { } } + void setTestCase(int) { + __testlib_fail("setTestCase not implemented in BufferedFileInputStreamReader"); + } + + std::vector getReadChars() { + __testlib_fail("getReadChars not implemented in BufferedFileInputStreamReader"); + } + int curChar() { if (!refill()) return EOFC; @@ -1673,6 +1967,9 @@ struct InStream { void init(std::FILE *f, TMode mode); + void setTestCase(int testCase); + std::vector getReadChars(); + /* Moves stream pointer to the first non-white-space character or EOF. */ void skipBlanks(); @@ -1706,9 +2003,9 @@ struct InStream { /* Moves pointer to the first non-white-space character and calls "eof()". */ bool seekEof(); - /* - * Checks that current position contains EOLN. - * If not it doesn't move stream pointer. + /* + * Checks that current position contains EOLN. + * If not it doesn't move stream pointer. * In strict mode expects "#13#10" for windows or "#10" for other platforms. */ bool eoln(); @@ -1719,9 +2016,9 @@ struct InStream { /* Moves stream pointer to the first character of the next line (if exists). */ void nextLine(); - /* - * Reads new token. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + /* + * Reads new token. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ std::string readWord(); @@ -1766,23 +2063,23 @@ struct InStream { void readTokenTo(std::string &result, const std::string &ptrn, const std::string &variableName = ""); - /* - * Reads new long long value. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + /* + * Reads new long long value. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ long long readLong(); unsigned long long readUnsignedLong(); /* - * Reads new int. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + * Reads new int. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ int readInteger(); /* - * Reads new int. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + * Reads new int. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ int readInt(); @@ -1830,15 +2127,15 @@ struct InStream { /* Reads space-separated sequence of integers. */ std::vector readInts(int size, int indexBase = 1); - /* - * Reads new double. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + /* + * Reads new double. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ double readReal(); /* - * Reads new double. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + * Reads new double. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ double readDouble(); @@ -1858,7 +2155,7 @@ struct InStream { std::vector readDoubles(int size, int indexBase = 1); - /* + /* * As "readReal()" but ensures that value in the range [minv,maxv] and * number of digit after the decimal point is in range [minAfterPointDigitCount,maxAfterPointDigitCount] * and number is in the form "[-]digit(s)[.digit(s)]". @@ -1871,7 +2168,7 @@ struct InStream { int minAfterPointDigitCount, int maxAfterPointDigitCount, const std::string &variablesName = "", int indexBase = 1); - /* + /* * As "readDouble()" but ensures that value in the range [minv,maxv] and * number of digit after the decimal point is in range [minAfterPointDigitCount,maxAfterPointDigitCount] * and number is in the form "[-]digit(s)[.digit(s)]". @@ -1913,9 +2210,9 @@ struct InStream { /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */ void readStringTo(std::string &result, const std::string &ptrn, const std::string &variableName = ""); - /* - * Reads line from the current position to EOLN or EOF. Moves stream pointer to - * the first character of the new line (if possible). + /* + * Reads line from the current position to EOLN or EOF. Moves stream pointer to + * the first character of the new line (if possible). */ std::string readLine(); @@ -1951,12 +2248,12 @@ struct InStream { /* Reads EOF or fails. Use it in validators. Calls "eof()" method internally. */ void readEof(); - /* + /* * Quit-functions aborts program with and : * input/answer streams replace any result to FAIL. */ NORETURN void quit(TResult result, const char *msg); - /* + /* * Quit-functions aborts program with and : * input/answer streams replace any result to FAIL. */ @@ -1967,13 +2264,13 @@ struct InStream { * input/answer streams replace any result to FAIL. */ void quitif(bool condition, TResult result, const char *msg, ...); - /* + /* * Quit-functions aborts program with and : * input/answer streams replace any result to FAIL. */ NORETURN void quits(TResult result, std::string msg); - /* + /* * Checks condition and aborts a program if condition is false. * Returns _wa for ouf and _fail on any other streams. */ @@ -2004,6 +2301,9 @@ struct InStream { void xmlSafeWrite(std::FILE *file, const char *msg); + /* Skips UTF-8 Byte Order Mark. */ + void skipBom(); + private: InStream(const InStream &); @@ -2040,9 +2340,19 @@ const double ValidatorBoundsHit::EPS = 1E-12; class Validator { private: + const static std::string TEST_MARKUP_HEADER; + const static std::string TEST_CASE_OPEN_TAG; + const static std::string TEST_CASE_CLOSE_TAG; + + bool _initialized; std::string _testset; std::string _group; + std::string _testOverviewLogFileName; + std::string _testMarkupFileName; + int _testCase = -1; + std::string _testCaseFileName; + std::map _boundsHitByVariableName; std::set _features; std::set _hitFeatures; @@ -2062,14 +2372,22 @@ class Validator { } public: - Validator() : _testset("tests"), _group() { + Validator() : _initialized(false), _testset("tests"), _group() { + } + + void initialize() { + _initialized = true; } std::string testset() const { + if (!_initialized) + __testlib_fail("Validator should be initialized with registerValidation(argc, argv) instead of registerValidation() to support validator.testset()"); return _testset; } std::string group() const { + if (!_initialized) + __testlib_fail("Validator should be initialized with registerValidation(argc, argv) instead of registerValidation() to support validator.group()"); return _group; } @@ -2077,6 +2395,18 @@ class Validator { return _testOverviewLogFileName; } + std::string testMarkupFileName() const { + return _testMarkupFileName; + } + + int testCase() const { + return _testCase; + } + + std::string testCaseFileName() const { + return _testCaseFileName; + } + void setTestset(const char *const testset) { _testset = testset; } @@ -2089,6 +2419,18 @@ class Validator { _testOverviewLogFileName = testOverviewLogFileName; } + void setTestMarkupFileName(const char *const testMarkupFileName) { + _testMarkupFileName = testMarkupFileName; + } + + void setTestCase(int testCase) { + _testCase = testCase; + } + + void setTestCaseFileName(const char *const testCaseFileName) { + _testCaseFileName = testCaseFileName; + } + void addBoundsHit(const std::string &variableName, ValidatorBoundsHit boundsHit) { if (isVariableNameBoundsAnalyzable(variableName)) { _boundsHitByVariableName[variableName] @@ -2130,7 +2472,7 @@ class Validator { _testOverviewLogFileName = ""; FILE *testOverviewLogFile = fopen(fileName.c_str(), "w"); if (NULL == testOverviewLogFile) - __testlib_fail("Validator::writeTestOverviewLog: can't test overview log to (" + fileName + ")"); + __testlib_fail("Validator::writeTestOverviewLog: can't write test overview log to (" + fileName + ")"); fprintf(testOverviewLogFile, "%s%s", getBoundsHitLog().c_str(), getFeaturesLog().c_str()); if (fclose(testOverviewLogFile)) __testlib_fail( @@ -2138,6 +2480,94 @@ class Validator { } } + void writeTestMarkup() { + if (!_testMarkupFileName.empty()) { + std::vector readChars = inf.getReadChars(); + if (!readChars.empty()) { + std::string markup(TEST_MARKUP_HEADER); + for (size_t i = 0; i < readChars.size(); i++) { + int c = readChars[i]; + if (i + 1 == readChars.size() && c == -1) + continue; + if (c <= 256) { + char cc = char(c); + if (cc == '\\' || cc == '!') + markup += '\\'; + markup += cc; + } else { + markup += TEST_CASE_OPEN_TAG; + markup += toString(c - 256); + markup += TEST_CASE_CLOSE_TAG; + } + } + FILE* f; + bool standard_file = false; + if (_testMarkupFileName == "stdout") + f = stdout, standard_file = true; + else if (_testMarkupFileName == "stderr") + f = stderr, standard_file = true; + else { + f = fopen(_testMarkupFileName.c_str(), "wb"); + if (NULL == f) + __testlib_fail("Validator::writeTestMarkup: can't write test markup to (" + _testMarkupFileName + ")"); + } + std::fprintf(f, "%s", markup.c_str()); + std::fflush(f); + if (!standard_file) + if (std::fclose(f)) + __testlib_fail("Validator::writeTestMarkup: can't close test markup file (" + _testCaseFileName + ")"); + } + } + } + + void writeTestCase() { + if (_testCase > 0) { + std::vector readChars = inf.getReadChars(); + if (!readChars.empty()) { + std::string content, testCaseContent; + bool matchedTestCase = false; + for (size_t i = 0; i < readChars.size(); i++) { + int c = readChars[i]; + if (i + 1 == readChars.size() && c == -1) + continue; + if (c <= 256) + content += char(c); + else { + if (matchedTestCase) { + testCaseContent = content; + matchedTestCase = false; + } + content = ""; + int testCase = c - 256; + if (testCase == _testCase) + matchedTestCase = true; + } + } + if (matchedTestCase) + testCaseContent = content; + + if (!testCaseContent.empty()) { + FILE* f; + bool standard_file = false; + if (_testCaseFileName.empty() || _testCaseFileName == "stdout") + f = stdout, standard_file = true; + else if (_testCaseFileName == "stderr") + f = stderr, standard_file = true; + else { + f = fopen(_testCaseFileName.c_str(), "wb"); + if (NULL == f) + __testlib_fail("Validator::writeTestCase: can't write test case to (" + _testCaseFileName + ")"); + } + std::fprintf(f, "%s", testCaseContent.c_str()); + std::fflush(f); + if (!standard_file) + if (std::fclose(f)) + __testlib_fail("Validator::writeTestCase: can't close test case file (" + _testCaseFileName + ")"); + } + } + } + } + void addFeature(const std::string &feature) { if (_features.count(feature)) __testlib_fail("Feature " + feature + " registered twice."); @@ -2158,8 +2588,14 @@ class Validator { } } validator; +const std::string Validator::TEST_MARKUP_HEADER = "MU\xF3\x01"; +const std::string Validator::TEST_CASE_OPEN_TAG = "!c"; +const std::string Validator::TEST_CASE_CLOSE_TAG = ";"; + struct TestlibFinalizeGuard { static bool alive; + static bool registered; + int quitCount, readEofCount; TestlibFinalizeGuard() : quitCount(0), readEofCount(0) { @@ -2176,14 +2612,29 @@ struct TestlibFinalizeGuard { if (testlibMode == _validator && readEofCount == 0 && quitCount == 0) __testlib_fail("Validator must end with readEof call."); + + /* opts */ + autoEnsureNoUnusedOpts(); + + if (!registered) + __testlib_fail("Call register-function in the first line of the main (registerTestlibCmd or other similar)"); } - validator.writeTestOverviewLog(); + if (__testlib_exitCode == 0) { + validator.writeTestOverviewLog(); + validator.writeTestMarkup(); + validator.writeTestCase(); + } } + +private: + /* opts */ + void autoEnsureNoUnusedOpts(); }; bool TestlibFinalizeGuard::alive = true; -TestlibFinalizeGuard testlibFinalizeGuard; +bool TestlibFinalizeGuard::registered = false; +extern TestlibFinalizeGuard testlibFinalizeGuard; /* * Call it to disable checks on finalization. @@ -2199,65 +2650,10 @@ std::fstream tout; /* implementation */ -#if __cplusplus > 199711L || defined(_MSC_VER) -template -static std::string vtos(const T &t, std::true_type) { - if (t == 0) - return "0"; - else { - T n(t); - bool negative = n < 0; - std::string s; - while (n != 0) { - T digit = n % 10; - if (digit < 0) - digit = -digit; - s += char('0' + digit); - n /= 10; - } - std::reverse(s.begin(), s.end()); - return negative ? "-" + s : s; - } -} - -template -static std::string vtos(const T &t, std::false_type) { - std::string s; - static std::stringstream ss; - ss.str(std::string()); - ss.clear(); - ss << t; - ss >> s; - return s; -} - -template -static std::string vtos(const T &t) { - return vtos(t, std::is_integral()); -} - -#else -template -static std::string vtos(const T& t) -{ - std::string s; - static std::stringstream ss; - ss.str(std::string()); - ss.clear(); - ss << t; - ss >> s; - return s; -} -#endif - -template -static std::string toString(const T &t) { - return vtos(t); -} - InStream::InStream() { reader = NULL; lastLine = -1; + opened = false; name = ""; mode = _input; strict = false; @@ -2274,6 +2670,7 @@ InStream::InStream(const InStream &baseStream, std::string content) { lastLine = -1; opened = true; strict = baseStream.strict; + stdfile = false; mode = baseStream.mode; name = "based on " + baseStream.name; readManyIteration = NO_INDEX; @@ -2290,6 +2687,38 @@ InStream::~InStream() { } } +void InStream::setTestCase(int testCase) { + if (testlibMode != _validator || mode != _input || !stdfile || this != &inf) + __testlib_fail("InStream::setTestCase can be used only for inf in validator-mode." + " Actually, prefer setTestCase function instead of InStream member"); + reader->setTestCase(testCase); +} + +std::vector InStream::getReadChars() { + if (testlibMode != _validator || mode != _input || !stdfile || this != &inf) + __testlib_fail("InStream::getReadChars can be used only for inf in validator-mode."); + return reader == NULL ? std::vector() : reader->getReadChars(); +} + +void setTestCase(int testCase) { + static bool first_run = true; + static bool zero_based = false; + + if (first_run && testCase == 0) + zero_based = true; + + if (zero_based) + testCase++; + + __testlib_hasTestCase = true; + __testlib_testCase = testCase; + + if (testlibMode == _validator) + inf.setTestCase(testCase); + + first_run = false; +} + #ifdef __GNUC__ __attribute__((const)) #endif @@ -2352,12 +2781,26 @@ void InStream::textColor( #endif } +#ifdef TESTLIB_THROW_EXIT_EXCEPTION_INSTEAD_OF_EXIT +class exit_exception: public std::exception { +private: + int exitCode; +public: + exit_exception(int exitCode): exitCode(exitCode) {} + int getExitCode() { return exitCode; } +}; +#endif + NORETURN void halt(int exitCode) { #ifdef FOOTER InStream::textColor(InStream::LightGray); std::fprintf(stderr, "Checker: \"%s\"\n", checkerName.c_str()); std::fprintf(stderr, "Exit code: %d\n", exitCode); InStream::textColor(InStream::LightGray); +#endif + __testlib_exitCode = exitCode; +#ifdef TESTLIB_THROW_EXIT_EXCEPTION_INSTEAD_OF_EXIT + throw exit_exception(exitCode); #endif std::exit(exitCode); } @@ -2371,13 +2814,13 @@ static std::string __testlib_appendMessage(const std::string &message, const std for (size_t i = 0; i < message.length(); i++) { if (message[i] == InStream::OPEN_BRACKET) { if (openPos == -1) - openPos = i; + openPos = int(i); else openPos = INT_MAX; } if (message[i] == InStream::CLOSE_BRACKET) { if (closePos == -1) - closePos = i; + closePos = int(i); else closePos = INT_MAX; } @@ -2402,13 +2845,13 @@ static std::string __testlib_toPrintableMessage(const std::string &message) { for (size_t i = 0; i < message.length(); i++) { if (message[i] == InStream::OPEN_BRACKET) { if (openPos == -1) - openPos = i; + openPos = int(i); else openPos = INT_MAX; } if (message[i] == InStream::CLOSE_BRACKET) { if (closePos == -1) - closePos = i; + closePos = int(i); else closePos = INT_MAX; } @@ -2456,6 +2899,9 @@ NORETURN void InStream::quit(TResult result, const char *msg) { result = _pe; #endif + if (testlibMode == _scorer && result != _fail) + quits(_fail, "Scorer should return points only. Don't use a quit function."); + if (mode != _output && result != _fail) { if (mode == _input && testlibMode == _validator && lastLine != -1) quits(_fail, __testlib_appendMessage(__testlib_appendMessage(message, name), "line " + vtos(lastLine))); @@ -2630,7 +3076,7 @@ void InStream::reset(std::FILE *file) { if (opened) close(); - if (!stdfile) + if (!stdfile && NULL == file) if (NULL == (file = std::fopen(name.c_str(), "rb"))) { if (mode == _output) quits(_pe, std::string("Output file not found: \"") + name + "\""); @@ -2641,7 +3087,6 @@ void InStream::reset(std::FILE *file) { if (NULL != file) { opened = true; - __testlib_set_binary(file); if (stdfile) @@ -2693,6 +3138,21 @@ void InStream::init(std::FILE *f, TMode mode) { reset(f); } +void InStream::skipBom() { + const std::string utf8Bom = "\xEF\xBB\xBF"; + size_t index = 0; + while (index < utf8Bom.size() && curChar() == utf8Bom[index]) { + index++; + skipChar(); + } + if (index < utf8Bom.size()) { + while (index != 0) { + unreadChar(utf8Bom[index - 1]); + index--; + } + } +} + char InStream::curChar() { return char(reader->curChar()); } @@ -2783,10 +3243,16 @@ void InStream::readTokenTo(std::string &result) { } static std::string __testlib_part(const std::string &s) { - if (s.length() <= 64) - return s; + std::string t; + for (size_t i = 0; i < s.length(); i++) + if (s[i] != '\0') + t += s[i]; + else + t += '~'; + if (t.length() <= 64) + return t; else - return s.substr(0, 30) + "..." + s.substr(s.length() - 31, 31); + return t.substr(0, 30) + "..." + t.substr(s.length() - 31, 31); } #define __testlib_readMany(readMany, readOne, typeName, space) \ @@ -3000,6 +3466,7 @@ static inline double stringToDouble(InStream &in, const char *buffer) { in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); char *suffix = new char[length + 1]; + std::memset(suffix, 0, length + 1); int scanned = std::sscanf(buffer, "%lf%s", &retval, suffix); bool empty = strlen(suffix) == 0; delete[] suffix; @@ -3012,8 +3479,15 @@ static inline double stringToDouble(InStream &in, const char *buffer) { in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); } -static inline double -stringToStrictDouble(InStream &in, const char *buffer, int minAfterPointDigitCount, int maxAfterPointDigitCount) { +static inline double stringToDouble(InStream &in, const std::string& buffer) { + for (size_t i = 0; i < buffer.length(); i++) + if (buffer[i] == '\0') + in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found (it contains \\0)").c_str()); + return stringToDouble(in, buffer.c_str()); +} + +static inline double stringToStrictDouble(InStream &in, const char *buffer, + int minAfterPointDigitCount, int maxAfterPointDigitCount) { if (minAfterPointDigitCount < 0) in.quit(_fail, "stringToStrictDouble: minAfterPointDigitCount should be non-negative."); @@ -3069,6 +3543,7 @@ stringToStrictDouble(InStream &in, const char *buffer, int minAfterPointDigitCou in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); char *suffix = new char[length + 1]; + std::memset(suffix, 0, length + 1); int scanned = std::sscanf(buffer, "%lf%s", &retval, suffix); bool empty = strlen(suffix) == 0; delete[] suffix; @@ -3083,6 +3558,14 @@ stringToStrictDouble(InStream &in, const char *buffer, int minAfterPointDigitCou in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); } +static inline double stringToStrictDouble(InStream &in, const std::string& buffer, + int minAfterPointDigitCount, int maxAfterPointDigitCount) { + for (size_t i = 0; i < buffer.length(); i++) + if (buffer[i] == '\0') + in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found (it contains \\0)").c_str()); + return stringToStrictDouble(in, buffer.c_str(), minAfterPointDigitCount, maxAfterPointDigitCount); +} + static inline long long stringToLongLong(InStream &in, const char *buffer) { if (strcmp(buffer, "-9223372036854775808") == 0) return LLONG_MIN; @@ -3099,7 +3582,7 @@ static inline long long stringToLongLong(InStream &in, const char *buffer) { long long retval = 0LL; int zeroes = 0; - int processingZeroes = true; + bool processingZeroes = true; for (int i = (minus ? 1 : 0); i < int(length); i++) { if (buffer[i] == '0' && processingZeroes) @@ -3129,6 +3612,13 @@ static inline long long stringToLongLong(InStream &in, const char *buffer) { in.quit(_pe, ("Expected int64, but \"" + __testlib_part(buffer) + "\" found").c_str()); } +static inline long long stringToLongLong(InStream &in, const std::string& buffer) { + for (size_t i = 0; i < buffer.length(); i++) + if (buffer[i] == '\0') + in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found (it contains \\0)").c_str()); + return stringToLongLong(in, buffer.c_str()); +} + static inline unsigned long long stringToUnsignedLongLong(InStream &in, const char *buffer) { size_t length = strlen(buffer); @@ -3147,7 +3637,7 @@ static inline unsigned long long stringToUnsignedLongLong(InStream &in, const ch if (length < 19) return retval; - if (length == 20 && strcmp(buffer, "18446744073709551615") == 1) + if (length == 20 && strcmp(buffer, "18446744073709551615") > 0) in.quit(_pe, ("Expected unsigned int64, but \"" + __testlib_part(buffer) + "\" found").c_str()); if (equals(retval, buffer)) @@ -3156,13 +3646,20 @@ static inline unsigned long long stringToUnsignedLongLong(InStream &in, const ch in.quit(_pe, ("Expected unsigned int64, but \"" + __testlib_part(buffer) + "\" found").c_str()); } +static inline long long stringToUnsignedLongLong(InStream &in, const std::string& buffer) { + for (size_t i = 0; i < buffer.length(); i++) + if (buffer[i] == '\0') + in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found (it contains \\0)").c_str()); + return stringToUnsignedLongLong(in, buffer.c_str()); +} + int InStream::readInteger() { if (!strict && seekEof()) quit(_unexpected_eof, "Unexpected end of file - int32 expected"); readWordTo(_tmpReadToken); - long long value = stringToLongLong(*this, _tmpReadToken.c_str()); + long long value = stringToLongLong(*this, _tmpReadToken); if (value < INT_MIN || value > INT_MAX) quit(_pe, ("Expected int32, but \"" + __testlib_part(_tmpReadToken) + "\" found").c_str()); @@ -3175,7 +3672,7 @@ long long InStream::readLong() { readWordTo(_tmpReadToken); - return stringToLongLong(*this, _tmpReadToken.c_str()); + return stringToLongLong(*this, _tmpReadToken); } unsigned long long InStream::readUnsignedLong() { @@ -3184,7 +3681,7 @@ unsigned long long InStream::readUnsignedLong() { readWordTo(_tmpReadToken); - return stringToUnsignedLongLong(*this, _tmpReadToken.c_str()); + return stringToUnsignedLongLong(*this, _tmpReadToken); } long long InStream::readLong(long long minv, long long maxv, const std::string &variableName) { @@ -3193,19 +3690,19 @@ long long InStream::readLong(long long minv, long long maxv, const std::string & if (result < minv || result > maxv) { if (readManyIteration == NO_INDEX) { if (variableName.empty()) - quit(_wa, ("Integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + + quit(_wa, ("Integer " + vtos(result) + " violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); else quit(_wa, ("Integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + - ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); } else { if (variableName.empty()) quit(_wa, ("Integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + - ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); else quit(_wa, ("Integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + - vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + vtos(result) + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); } } @@ -3232,20 +3729,20 @@ InStream::readUnsignedLong(unsigned long long minv, unsigned long long maxv, con if (readManyIteration == NO_INDEX) { if (variableName.empty()) quit(_wa, - ("Unsigned integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + + ("Unsigned integer " + vtos(result) + " violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); else quit(_wa, ("Unsigned integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + - ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); } else { if (variableName.empty()) quit(_wa, ("Unsigned integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + - ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); else quit(_wa, ("Unsigned integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + - "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + + "] equals to " + vtos(result) + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); } } @@ -3280,19 +3777,19 @@ int InStream::readInt(int minv, int maxv, const std::string &variableName) { if (result < minv || result > maxv) { if (readManyIteration == NO_INDEX) { if (variableName.empty()) - quit(_wa, ("Integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + + quit(_wa, ("Integer " + vtos(result) + " violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); else quit(_wa, ("Integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + - ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); } else { if (variableName.empty()) quit(_wa, ("Integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + - ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); else quit(_wa, ("Integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + - vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + vtos(result) + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); } } @@ -3326,7 +3823,7 @@ double InStream::readReal() { if (!strict && seekEof()) quit(_unexpected_eof, "Unexpected end of file - double expected"); - return stringToDouble(*this, readWord().c_str()); + return stringToDouble(*this, readWord()); } double InStream::readDouble() { @@ -3392,8 +3889,7 @@ double InStream::readStrictReal(double minv, double maxv, if (!strict && seekEof()) quit(_unexpected_eof, "Unexpected end of file - strict double expected"); - double result = stringToStrictDouble(*this, readWord().c_str(), - minAfterPointDigitCount, maxAfterPointDigitCount); + double result = stringToStrictDouble(*this, readWord(), minAfterPointDigitCount, maxAfterPointDigitCount); if (result < minv || result > maxv) { if (readManyIteration == NO_INDEX) { @@ -3745,6 +4241,15 @@ NORETURN void quitp(int points, const std::string &message = "") { __testlib_quitp(points, message.c_str()); } +NORETURN void quitpi(const std::string &points_info, const std::string &message = "") { + if (points_info.find(' ') != std::string::npos) + quit(_fail, "Parameter 'points_info' can't contain spaces"); + if (message.empty()) + quit(_points, ("points_info=" + points_info).c_str()); + else + quit(_points, ("points_info=" + points_info + " " + message).c_str()); +} + template #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) @@ -3787,8 +4292,9 @@ NORETURN void __testlib_help() { std::fprintf(stderr, "\n"); std::fprintf(stderr, "Program must be run with the following arguments: \n"); - std::fprintf(stderr, " [ [<-appes>]]\n\n"); + std::fprintf(stderr, " [--testset testset] [--group group] [ [<-appes>]]\n\n"); + __testlib_exitCode = FAIL_EXIT_CODE; std::exit(FAIL_EXIT_CODE); } @@ -3812,12 +4318,41 @@ static void __testlib_ensuresPreconditions() { quit(_fail, "Function __testlib_isNaN is not working correctly: possible reason is '-ffast-math'"); } +std::string __testlib_testset; + +std::string getTestset() { + return __testlib_testset; +} + +std::string __testlib_group; + +std::string getGroup() { + return __testlib_group; +} + +static void __testlib_set_testset_and_group(int argc, char* argv[]) { + for (int i = 1; i < argc; i++) { + if (!strcmp("--testset", argv[i])) { + if (i + 1 < argc && strlen(argv[i + 1]) > 0) + __testlib_testset = argv[++i]; + else + quit(_fail, std::string("Expected non-empty testset after --testset command line parameter")); + } else if (!strcmp("--group", argv[i])) { + if (i + 1 < argc) + __testlib_group = argv[++i]; + else + quit(_fail, std::string("Expected group after --group command line parameter")); + } + } +} + void registerGen(int argc, char *argv[], int randomGeneratorVersion) { if (randomGeneratorVersion < 0 || randomGeneratorVersion > 1) quitf(_fail, "Random generator version is expected to be 0 or 1."); random_t::version = randomGeneratorVersion; __testlib_ensuresPreconditions(); + TestlibFinalizeGuard::registered = true; testlibMode = _generator; __testlib_set_binary(stdin); @@ -3861,6 +4396,8 @@ void registerGen(int argc, char *argv[]) { void registerInteraction(int argc, char *argv[]) { __testlib_ensuresPreconditions(); + __testlib_set_testset_and_group(argc, argv); + TestlibFinalizeGuard::registered = true; testlibMode = _interactor; __testlib_set_binary(stdin); @@ -3912,9 +4449,13 @@ void registerInteraction(int argc, char *argv[]) { void registerValidation() { __testlib_ensuresPreconditions(); + TestlibFinalizeGuard::registered = true; testlibMode = _validator; + __testlib_set_binary(stdin); + __testlib_set_binary(stdout); + __testlib_set_binary(stderr); inf.init(stdin, _input); inf.strict = true; @@ -3922,28 +4463,60 @@ void registerValidation() { void registerValidation(int argc, char *argv[]) { registerValidation(); + __testlib_set_testset_and_group(argc, argv); + + validator.initialize(); + TestlibFinalizeGuard::registered = true; + + std::string comment = "Validator must be run with the following arguments:" + " [--testset testset]" + " [--group group]" + " [--testOverviewLogFileName fileName]" + " [--testMarkupFileName fileName]" + " [--testCase testCase]" + " [--testCaseFileName fileName]" + ; for (int i = 1; i < argc; i++) { if (!strcmp("--testset", argv[i])) { if (i + 1 < argc && strlen(argv[i + 1]) > 0) validator.setTestset(argv[++i]); else - quit(_fail, std::string("Validator must be run with the following arguments: ") + - "[--testset testset] [--group group] [--testOverviewLogFileName fileName]"); + quit(_fail, comment); } if (!strcmp("--group", argv[i])) { if (i + 1 < argc) validator.setGroup(argv[++i]); else - quit(_fail, std::string("Validator must be run with the following arguments: ") + - "[--testset testset] [--group group] [--testOverviewLogFileName fileName]"); + quit(_fail, comment); } if (!strcmp("--testOverviewLogFileName", argv[i])) { if (i + 1 < argc) validator.setTestOverviewLogFileName(argv[++i]); else - quit(_fail, std::string("Validator must be run with the following arguments: ") + - "[--testset testset] [--group group] [--testOverviewLogFileName fileName]"); + quit(_fail, comment); + } + if (!strcmp("--testMarkupFileName", argv[i])) { + if (i + 1 < argc) + validator.setTestMarkupFileName(argv[++i]); + else + quit(_fail, comment); + } + if (!strcmp("--testCase", argv[i])) { + if (i + 1 < argc) { + long long testCase = stringToLongLong(inf, argv[++i]); + if (testCase < 1 || testCase >= __TESTLIB_MAX_TEST_CASE) + quit(_fail, format("Argument testCase should be between 1 and %d, but ", __TESTLIB_MAX_TEST_CASE) + + toString(testCase) + " found"); + validator.setTestCase(int(testCase)); + } else + quit(_fail, comment); + } + if (!strcmp("--testCaseFileName", argv[i])) { + if (i + 1 < argc) { + validator.setTestCaseFileName(argv[++i]); + } else + quit(_fail, comment); } } } @@ -3960,18 +4533,74 @@ void feature(const std::string &feature) { validator.feature(feature); } +class Checker { +private: + bool _initialized; + std::string _testset; + std::string _group; + +public: + Checker() : _initialized(false), _testset("tests"), _group() { + } + + void initialize() { + _initialized = true; + } + + std::string testset() const { + if (!_initialized) + __testlib_fail("Checker should be initialized with registerTestlibCmd(argc, argv) instead of registerTestlibCmd() to support checker.testset()"); + return _testset; + } + + std::string group() const { + if (!_initialized) + __testlib_fail("Checker should be initialized with registerTestlibCmd(argc, argv) instead of registerTestlibCmd() to support checker.group()"); + return _group; + } + + void setTestset(const char *const testset) { + _testset = testset; + } + + void setGroup(const char *const group) { + _group = group; + } +} checker; + void registerTestlibCmd(int argc, char *argv[]) { __testlib_ensuresPreconditions(); + __testlib_set_testset_and_group(argc, argv); + TestlibFinalizeGuard::registered = true; testlibMode = _checker; __testlib_set_binary(stdin); - if (argc > 1 && !strcmp("--help", argv[1])) + std::vector args(1, argv[0]); + checker.initialize(); + + for (int i = 1; i < argc; i++) { + if (!strcmp("--testset", argv[i])) { + if (i + 1 < argc && strlen(argv[i + 1]) > 0) + checker.setTestset(argv[++i]); + else + quit(_fail, std::string("Expected testset after --testset command line parameter")); + } else if (!strcmp("--group", argv[i])) { + if (i + 1 < argc) + checker.setGroup(argv[++i]); + else + quit(_fail, std::string("Expected group after --group command line parameter")); + } else + args.push_back(argv[i]); + } + + argc = int(args.size()); + if (argc > 1 && "--help" == args[1]) __testlib_help(); if (argc < 4 || argc > 6) { quit(_fail, std::string("Program must be run with the following arguments: ") + - std::string(" [ [<-appes>]]") + + std::string("[--testset testset] [--group group] [ [<-appes>]]") + "\nUse \"--help\" to get help information"); } @@ -3981,23 +4610,24 @@ void registerTestlibCmd(int argc, char *argv[]) { } if (argc == 5) { - resultName = argv[4]; + resultName = args[4]; appesMode = false; } if (argc == 6) { - if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5])) { + if ("-APPES" != args[5] && "-appes" != args[5]) { quit(_fail, std::string("Program must be run with the following arguments: ") + " [ [<-appes>]]"); } else { - resultName = argv[4]; + resultName = args[4]; appesMode = true; } } - inf.init(argv[1], _input); - ouf.init(argv[2], _output); - ans.init(argv[3], _answer); + inf.init(args[1], _input); + ouf.init(args[2], _output); + ouf.skipBom(); + ans.init(args[3], _answer); } void registerTestlib(int argc, ...) { @@ -4033,6 +4663,9 @@ static inline void __testlib_ensure(bool cond, const char *msg) { } #define ensure(cond) __testlib_ensure(cond, "Condition failed: \"" #cond "\"") +#define STRINGIZE_DETAIL(x) #x +#define STRINGIZE(x) STRINGIZE_DETAIL(x) +#define ensure_ext(cond) __testlib_ensure(cond, "Line " STRINGIZE(__LINE__) ": Condition failed: \"" #cond "\"") #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) @@ -4056,7 +4689,7 @@ void setName(const char *format, ...) { checkerName = name; } -/* +/* * Do not use random_shuffle, because it will produce different result * for different C++ compilers. * @@ -4115,7 +4748,7 @@ void srand(unsigned int seed) RAND_THROW_STATEMENT quitf(_fail, "Don't use srand(), you should use " "'registerGen(argc, argv, 1);' to initialize generator seed " "by hash code of the command line params. The third parameter " - "is randomGeneratorVersion (currently the latest is 1) [ignored seed=%d].", seed); + "is randomGeneratorVersion (currently the latest is 1) [ignored seed=%u].", seed); } void startTest(int test) { @@ -4531,9 +5164,34 @@ void println(const A &a, const B &b, const C &c, const D &d, const E &e, const F } /* opts */ -size_t getOptType(char* s) { + +/** + * A struct for a singular testlib opt, containing the raw string value, + * and a boolean value for marking whether the opt is used. + */ +struct TestlibOpt { + std::string value; + bool used; + + TestlibOpt() : value(), used(false) {} +}; + +/** + * Get the type of opt based on the number of `-` at the beginning and the + * _validity_ of the key name. + * + * A valid key name must start with an alphabetical character. + * + * Returns: 1 if s has one `-` at the beginning, that is, "-keyName". + * 2 if s has two `-` at the beginning, that is, "--keyName". + * 0 otherwise. That is, if s has no `-` at the beginning, or has more + * than 2 at the beginning ("---keyName", "----keyName", ...), or the + * keyName is invalid (the first character is not an alphabetical + * character). + */ +size_t getOptType(char *s) { if (!s || strlen(s) <= 1) - return false; + return 0; if (s[0] == '-') { if (isalpha(s[1])) @@ -4545,7 +5203,36 @@ size_t getOptType(char* s) { return 0; } -size_t parseOpt(size_t argc, char* argv[], size_t index, std::map& opts) { +/** + * Parse the opt at a given index, and put it into the opts maps. + * + * An opt can has the following form: + * 1) -keyName=value or --keyName=value (ex. -n=10 --test-count=20) + * 2) -keyName value or --keyName value (ex. -n 10 --test-count 20) + * 3) -kNumval or --kNumval (ex. -n10 --t20) + * 4) -boolProperty or --boolProperty (ex. -sorted --tree-only) + * + * Only the second form consumes 2 arguments. The other consumes only 1 + * argument. + * + * In the third form, the key is a single character, and after the key is the + * value. The value _should_ be a number. + * + * In the forth form, the value is true. + * + * Params: + * - argc and argv: the number of command line arguments and the command line + * arguments themselves. + * - index: the starting index of the opts. + * - opts: the map containing the resulting opt. + * + * Returns: the number of consumed arguments to parse the opt. + * 0 if there is no arguments to parse. + * + * Algorithm details: + * TODO. Please refer to the implementation to see how the code handles the 3rd and 4th forms separately. + */ +size_t parseOpt(size_t argc, char *argv[], size_t index, std::map &opts) { if (index >= argc) return 0; @@ -4569,7 +5256,7 @@ size_t parseOpt(size_t argc, char* argv[], size_t index, std::map __testlib_argv; -std::map __testlib_opts; -void prepareOpts(int argc, char* argv[]) { +/** + * Global dictionary containing all the parsed opts. + */ +std::map __testlib_opts; + +/** + * Whether automatic no unused opts ensurement should be done. This flag will + * be turned on when `has_opt` or `opt(key, default_value)` is called. + * + * The automatic ensurement can be suppressed when + * __testlib_ensureNoUnusedOptsSuppressed is true. + */ +bool __testlib_ensureNoUnusedOptsFlag = false; + +/** + * Suppress no unused opts automatic ensurement. Can be set to true with + * `suppressEnsureNoUnusedOpts()`. + */ +bool __testlib_ensureNoUnusedOptsSuppressed = false; + +/** + * Parse command line arguments into opts. + * The results are stored into __testlib_argv and __testlib_opts. + */ +void prepareOpts(int argc, char *argv[]) { if (argc <= 0) __testlib_fail("Opts: expected argc>=0 but found " + toString(argc)); size_t n = static_cast(argc); // NOLINT(hicpp-use-auto,modernize-use-auto) - __testlib_opts = std::map(); + __testlib_opts = std::map(); for (size_t index = 1; index < n; index += parseOpt(n, argv, index, __testlib_opts)); __testlib_argv = std::vector(n); for (size_t index = 0; index < n; index++) __testlib_argv[index] = argv[index]; } +/** + * An utility function to get the argument with a given index. This function + * also print a readable message when no arguments are found. + */ std::string __testlib_indexToArgv(int index) { if (index < 0 || index >= int(__testlib_argv.size())) - __testlib_fail("Opts: index '" + toString(index) + "' is out of range [0," + toString(__testlib_argv.size()) + ")"); + __testlib_fail("Opts: index '" + toString(index) + "' is out of range [0," + + toString(__testlib_argv.size()) + ")"); return __testlib_argv[size_t(index)]; } -std::string __testlib_keyToOpts(const std::string& key) { - if (__testlib_opts.count(key) == 0) +/** + * An utility function to get the opt with a given key . This function + * also print a readable message when no opts are found. + */ +std::string __testlib_keyToOpts(const std::string &key) { + auto it = __testlib_opts.find(key); + if (it == __testlib_opts.end()) __testlib_fail("Opts: unknown key '" + compress(key) + "'"); - return __testlib_opts[key]; + it->second.used = true; + return it->second.value; } template -T optValueToIntegral(const std::string& s, bool nonnegative); +T optValueToIntegral(const std::string &s, bool nonnegative); -long double optValueToLongDouble(const std::string& s); +long double optValueToLongDouble(const std::string &s); -std::string parseExponentialOptValue(const std::string& s) { +std::string parseExponentialOptValue(const std::string &s) { size_t pos = std::string::npos; for (size_t i = 0; i < s.length(); i++) if (s[i] == 'e' || s[i] == 'E') { - if (pos >= 0) + if (pos != std::string::npos) __testlib_fail("Opts: expected typical exponential notation but '" + compress(s) + "' found"); pos = i; } @@ -4671,7 +5395,7 @@ std::string parseExponentialOptValue(const std::string& s) { } template -T optValueToIntegral(const std::string& s_, bool nonnegative) { +T optValueToIntegral(const std::string &s_, bool nonnegative) { std::string s(parseExponentialOptValue(s_)); if (s.empty()) __testlib_fail("Opts: expected integer but '" + compress(s_) + "' found"); @@ -4698,7 +5422,7 @@ T optValueToIntegral(const std::string& s_, bool nonnegative) { return value; } -long double optValueToLongDouble(const std::string& s_) { +long double optValueToLongDouble(const std::string &s_) { std::string s(parseExponentialOptValue(s_)); if (s.empty()) __testlib_fail("Opts: expected float number but '" + compress(s_) + "' found"); @@ -4733,98 +5457,488 @@ long double optValueToLongDouble(const std::string& s_) { return value; } +/** + * Return true if there is an opt with a given key. + * + * By calling this function, automatic ensurement for no unused opts will be + * done when the program is finalized. Call suppressEnsureNoUnusedOpts() to + * turn it off. + */ +bool has_opt(const std::string &key) { + __testlib_ensureNoUnusedOptsFlag = true; + return __testlib_opts.count(key) != 0; +} + +/* About the following part for opt with 2 and 3 arguments. + * + * To parse the argv/opts correctly for a give type (integer, floating point or + * string), some meta programming must be done to determine the type of + * the type, and use the correct parsing function accordingly. + * + * The pseudo algorithm for determining the type of T and parse it accordingly + * is as follows: + * + * if (T is integral type) { + * if (T is unsigned) { + * parse the argv/opt as an **unsigned integer** of type T. + * } else { + * parse the argv/opt as an **signed integer** of type T. + * } else { + * if (T is floating point type) { + * parse the argv/opt as an **floating point** of type T. + * } else { + * // T should be std::string + * just the raw content of the argv/opts. + * } + * } + * + * To help with meta programming, some `opt` function with 2 or 3 arguments are + * defined. + * + * Opt with 3 arguments: T opt(true/false is_integral, true/false is_unsigned, index/key) + * + * + The first argument is for determining whether the type T is an integral + * type. That is, the result of std::is_integral() should be passed to + * this argument. When false, the type _should_ be either floating point or a + * std::string. + * + * + The second argument is for determining whether the signedness of the type + * T (if it is unsigned or signed). That is, the result of + * std::is_unsigned() should be passed to this argument. This argument can + * be ignored if the first one is false, because it only applies to integer. + * + * Opt with 2 arguments: T opt(true/false is_floating_point, index/key) + * + The first argument is for determining whether the type T is a floating + * point type. That is, the result of std::is_floating_point() should be + * passed to this argument. When false, the type _should_ be a std::string. + */ + template -T opt(std::false_type, int index); +T opt(std::false_type is_floating_point, int index); template<> -std::string opt(std::false_type, int index) { +std::string opt(std::false_type /*is_floating_point*/, int index) { return __testlib_indexToArgv(index); } template -T opt(std::true_type, int index) { +T opt(std::true_type /*is_floating_point*/, int index) { return T(optValueToLongDouble(__testlib_indexToArgv(index))); } template -T opt(std::false_type, U, int index) { +T opt(std::false_type /*is_integral*/, U /*is_unsigned*/, int index) { return opt(std::is_floating_point(), index); } template -T opt(std::true_type, std::false_type, int index) { +T opt(std::true_type /*is_integral*/, std::false_type /*is_unsigned*/, int index) { return optValueToIntegral(__testlib_indexToArgv(index), false); } template -T opt(std::true_type, std::true_type, int index) { +T opt(std::true_type /*is_integral*/, std::true_type /*is_unsigned*/, int index) { return optValueToIntegral(__testlib_indexToArgv(index), true); } template<> -bool opt(std::true_type, std::true_type, int index) { +bool opt(std::true_type /*is_integral*/, std::true_type /*is_unsigned*/, int index) { std::string value = __testlib_indexToArgv(index); if (value == "true" || value == "1") return true; if (value == "false" || value == "0") return false; - __testlib_fail("Opts: opt by index '" + toString(index) + "': expected bool true/false or 0/1 but '" + compress(value) + "' found"); + __testlib_fail("Opts: opt by index '" + toString(index) + "': expected bool true/false or 0/1 but '" + + compress(value) + "' found"); } +/** + * Return the parsed argv by a given index. + */ template T opt(int index) { return opt(std::is_integral(), std::is_unsigned(), index); } +/** + * Return the raw string value of an argv by a given index. + */ std::string opt(int index) { return opt(index); } +/** + * Return the parsed argv by a given index. If the index is bigger than + * the number of argv, return the given default_value. + */ +template +T opt(int index, const T &default_value) { + if (index >= int(__testlib_argv.size())) { + return default_value; + } + return opt(index); +} + +/** + * Return the raw string value of an argv by a given index. If the index is + * bigger than the number of argv, return the given default_value. + */ +std::string opt(int index, const std::string &default_value) { + return opt(index, default_value); +} + template -T opt(std::false_type, const std::string& key); +T opt(std::false_type is_floating_point, const std::string &key); template<> -std::string opt(std::false_type, const std::string& key) { +std::string opt(std::false_type /*is_floating_point*/, const std::string &key) { return __testlib_keyToOpts(key); } template -T opt(std::true_type, const std::string& key) { +T opt(std::true_type /*is_integral*/, const std::string &key) { return T(optValueToLongDouble(__testlib_keyToOpts(key))); } template -T opt(std::false_type, U, const std::string& key) { +T opt(std::false_type /*is_integral*/, U, const std::string &key) { return opt(std::is_floating_point(), key); } template -T opt(std::true_type, std::false_type, const std::string& key) { +T opt(std::true_type /*is_integral*/, std::false_type /*is_unsigned*/, const std::string &key) { return optValueToIntegral(__testlib_keyToOpts(key), false); } template -T opt(std::true_type, std::true_type, const std::string& key) { +T opt(std::true_type /*is_integral*/, std::true_type /*is_unsigned*/, const std::string &key) { return optValueToIntegral(__testlib_keyToOpts(key), true); } template<> -bool opt(std::true_type, std::true_type, const std::string& key) { +bool opt(std::true_type /*is_integral*/, std::true_type /*is_unsigned*/, const std::string &key) { + if (!has_opt(key)) + return false; std::string value = __testlib_keyToOpts(key); if (value == "true" || value == "1") return true; if (value == "false" || value == "0") return false; - __testlib_fail("Opts: key '" + compress(key) + "': expected bool true/false or 0/1 but '" + compress(value) + "' found"); + __testlib_fail("Opts: key '" + compress(key) + "': expected bool true/false or 0/1 but '" + + compress(value) + "' found"); } +/** + * Return the parsed opt by a given key. + */ template -T opt(const std::string key) { +T opt(const std::string &key) { return opt(std::is_integral(), std::is_unsigned(), key); } -std::string opt(const std::string key) { +/** + * Return the raw string value of an opt by a given key + */ +std::string opt(const std::string &key) { return opt(key); } + +/* Scorer started. */ + +enum TestResultVerdict { + SKIPPED, + OK, + WRONG_ANSWER, + RUNTIME_ERROR, + TIME_LIMIT_EXCEEDED, + IDLENESS_LIMIT_EXCEEDED, + MEMORY_LIMIT_EXCEEDED, + COMPILATION_ERROR, + CRASHED, + FAILED +}; + +std::string serializeVerdict(TestResultVerdict verdict) { + switch (verdict) { + case SKIPPED: return "SKIPPED"; + case OK: return "OK"; + case WRONG_ANSWER: return "WRONG_ANSWER"; + case RUNTIME_ERROR: return "RUNTIME_ERROR"; + case TIME_LIMIT_EXCEEDED: return "TIME_LIMIT_EXCEEDED"; + case IDLENESS_LIMIT_EXCEEDED: return "IDLENESS_LIMIT_EXCEEDED"; + case MEMORY_LIMIT_EXCEEDED: return "MEMORY_LIMIT_EXCEEDED"; + case COMPILATION_ERROR: return "COMPILATION_ERROR"; + case CRASHED: return "CRASHED"; + case FAILED: return "FAILED"; + } + throw "Unexpected verdict"; +} + +TestResultVerdict deserializeTestResultVerdict(std::string s) { + if (s == "SKIPPED") + return SKIPPED; + else if (s == "OK") + return OK; + else if (s == "WRONG_ANSWER") + return WRONG_ANSWER; + else if (s == "RUNTIME_ERROR") + return RUNTIME_ERROR; + else if (s == "TIME_LIMIT_EXCEEDED") + return TIME_LIMIT_EXCEEDED; + else if (s == "IDLENESS_LIMIT_EXCEEDED") + return IDLENESS_LIMIT_EXCEEDED; + else if (s == "MEMORY_LIMIT_EXCEEDED") + return MEMORY_LIMIT_EXCEEDED; + else if (s == "COMPILATION_ERROR") + return COMPILATION_ERROR; + else if (s == "CRASHED") + return CRASHED; + else if (s == "FAILED") + return FAILED; + ensuref(false, "Unexpected serialized TestResultVerdict"); + // No return actually. + return FAILED; +} + +struct TestResult { + int testIndex; + std::string testset; + std::string group; + TestResultVerdict verdict; + double points; + long long timeConsumed; + long long memoryConsumed; + std::string input; + std::string output; + std::string answer; + int exitCode; + std::string checkerComment; +}; + +std::string serializePoints(double points) { + if (std::isnan(points)) + return ""; + else { + char c[64]; + snprintf(c, 64, "%.03lf", points); + return c; + } +} + +double deserializePoints(std::string s) { + if (s.empty()) + return std::numeric_limits::quiet_NaN(); + else { + double result; + ensuref(sscanf(s.c_str(), "%lf", &result) == 1, "Invalid serialized points"); + return result; + } +} + +std::string escapeTestResultString(std::string s) { + std::string result; + for (size_t i = 0; i < s.length(); i++) { + if (s[i] == '\r') + continue; + if (s[i] == '\n') { + result += "\\n"; + continue; + } + if (s[i] == '\\' || s[i] == ';') + result += '\\'; + result += s[i]; + } + return result; +} + +std::string unescapeTestResultString(std::string s) { + std::string result; + for (size_t i = 0; i < s.length(); i++) { + if (s[i] == '\\' && i + 1 < s.length()) { + if (s[i + 1] == 'n') { + result += '\n'; + i++; + continue; + } else if (s[i + 1] == ';' || s[i + 1] == '\\') { + result += s[i + 1]; + i++; + continue; + } + } + result += s[i]; + } + return result; +} + +std::string serializeTestResult(TestResult tr) { + std::string result; + result += std::to_string(tr.testIndex); + result += ";"; + result += escapeTestResultString(tr.testset); + result += ";"; + result += escapeTestResultString(tr.group); + result += ";"; + result += serializeVerdict(tr.verdict); + result += ";"; + result += serializePoints(tr.points); + result += ";"; + result += std::to_string(tr.timeConsumed); + result += ";"; + result += std::to_string(tr.memoryConsumed); + result += ";"; + result += escapeTestResultString(tr.input); + result += ";"; + result += escapeTestResultString(tr.output); + result += ";"; + result += escapeTestResultString(tr.answer); + result += ";"; + result += std::to_string(tr.exitCode); + result += ";"; + result += escapeTestResultString(tr.checkerComment); + return result; +} + +TestResult deserializeTestResult(std::string s) { + std::vector items; + std::string t; + for (size_t i = 0; i < s.length(); i++) { + if (s[i] == '\\') { + t += s[i]; + if (i + 1 < s.length()) + t += s[i + 1]; + i++; + continue; + } else { + if (s[i] == ';') { + items.push_back(t); + t = ""; + } else + t += s[i]; + } + } + items.push_back(t); + + ensuref(items.size() == 12, "Invalid TestResult serialization: expected exactly 12 items"); + + TestResult tr; + size_t pos = 0; + tr.testIndex = stoi(items[pos++]); + tr.testset = unescapeTestResultString(items[pos++]); + tr.group = unescapeTestResultString(items[pos++]); + tr.verdict = deserializeTestResultVerdict(items[pos++]); + tr.points = deserializePoints(items[pos++]); + tr.timeConsumed = stoll(items[pos++]); + tr.memoryConsumed = stoll(items[pos++]); + tr.input = unescapeTestResultString(items[pos++]); + tr.output = unescapeTestResultString(items[pos++]); + tr.answer = unescapeTestResultString(items[pos++]); + tr.exitCode = stoi(items[pos++]); + tr.checkerComment = unescapeTestResultString(items[pos++]); + + return tr; +} + +std::vector readTestResults(std::string fileName) { + std::ifstream stream; + stream.open(fileName.c_str(), std::ios::in); + ensuref(stream.is_open(), "Can't read test results file '%s'", fileName.c_str()); + std::vector result; + std::string line; + while (getline(stream, line)) + if (!line.empty()) + result.push_back(deserializeTestResult(line)); + stream.close(); + return result; +} + +std::function)> __testlib_scorer; + +struct TestlibScorerGuard { + ~TestlibScorerGuard() { + if (testlibMode == _scorer) { + std::vector testResults; + while (!inf.eof()) { + std::string line = inf.readLine(); + if (!line.empty()) + testResults.push_back(deserializeTestResult(line)); + } + inf.readEof(); + printf("%.3f\n", __testlib_scorer(testResults)); + } + } +} __testlib_scorer_guard; + +void registerScorer(int argc, char *argv[], std::function)> scorer) { + /* Suppress unused. */ + (void)(argc), (void)(argv); + + __testlib_ensuresPreconditions(); + + testlibMode = _scorer; + __testlib_set_binary(stdin); + + inf.init(stdin, _input); + inf.strict = false; + + __testlib_scorer = scorer; +} + +/* Scorer ended. */ + +/** + * Return the parsed opt by a given key. If no opts with the given key are + * found, return the given default_value. + * + * By calling this function, automatic ensurement for no unused opts will be + * done when the program is finalized. Call suppressEnsureNoUnusedOpts() to + * turn it off. + */ +template +T opt(const std::string &key, const T &default_value) { + if (!has_opt(key)) { + return default_value; + } + return opt(key); +} + +/** + * Return the raw string value of an opt by a given key. If no opts with the + * given key are found, return the given default_value. + * + * By calling this function, automatic ensurement for no unused opts will be + * done when the program is finalized. Call suppressEnsureNoUnusedOpts() to + * turn it off. + */ +std::string opt(const std::string &key, const std::string &default_value) { + return opt(key, default_value); +} + +/** + * Check if all opts are used. If not, __testlib_fail is called. + * Should be used after calling all opt() function calls. + * + * This function is useful when opt() with default_value for checking typos + * in the opt's key. + */ +void ensureNoUnusedOpts() { + for (const auto &opt: __testlib_opts) { + if (!opt.second.used) { + __testlib_fail(format("Opts: unused key '%s'", compress(opt.first).c_str())); + } + } +} + +void suppressEnsureNoUnusedOpts() { + __testlib_ensureNoUnusedOptsSuppressed = true; +} + +void TestlibFinalizeGuard::autoEnsureNoUnusedOpts() { + if (__testlib_ensureNoUnusedOptsFlag && !__testlib_ensureNoUnusedOptsSuppressed) { + ensureNoUnusedOpts(); + } +} + +TestlibFinalizeGuard testlibFinalizeGuard; + #endif #endif diff --git a/tests/lib/windows-kit-10.0.19041.0-include.7z b/tests/lib/windows-kit-10.0.19041.0-include.7z deleted file mode 100644 index b75b3fb..0000000 Binary files a/tests/lib/windows-kit-10.0.19041.0-include.7z and /dev/null differ diff --git a/tests/run.sh b/tests/run.sh index 69f76b2..4ae02a0 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,5 +1,8 @@ #!/bin/bash -set -eo pipefail +set -e -o pipefail + +echo "Checking installed Java" +java -version TESTS_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) export TESTS_DIR="$TESTS_DIR" @@ -17,6 +20,10 @@ ARGS_CPP_STANDARDS="," ARGS_CPP_VERSIONS="," ARGS_TESTS="," ARGS_CPP_BITS="" + +MSVC_INCLUDE_YEAR="2022" +WINKIT_INCLUDE_VERSION="10.0.19041.0" + for arg in "$@"; do if [[ "$arg" == test-* ]]; then ARGS_TESTS="${ARGS_TESTS}${arg}," @@ -104,12 +111,12 @@ MSYS*) machine=Windows ;; esac export MACHINE="$machine" -if [[ "$machine" == "Windows" && ("$ARGS_CPP" == "" || "$ARGS_CPP" == "msvc") ]]; then - for f in msvc-2022-include windows-kit-10.0.19041.0-include; do - rm -rf "${TESTS_DIR:?}"/lib/$f && mkdir -p "$TESTS_DIR"/lib/$f - 7z x -o"${TESTS_DIR:?}"/lib/$f "$TESTS_DIR"/lib/$f.7z - done -fi +#if [[ "$machine" == "Windows" && ("$ARGS_CPP" == "" || "$ARGS_CPP" == "clang++") ]]; then +# for f in msvc-${MSVC_INCLUDE_YEAR}-include windows-kit-${WINKIT_INCLUDE_VERSION}-include; do +# rm -rf "${TESTS_DIR:?}"/lib/$f && mkdir -p "$TESTS_DIR"/lib/$f +# 7z x -o"${TESTS_DIR:?}"/lib/$f "$TESTS_DIR"/lib/$f.7z +# done +#fi run_tests() { export INVOCATION_ID=$RANDOM @@ -170,9 +177,10 @@ if [[ "$machine" == "Windows" && ("$ARGS_CPP" == "" || "$ARGS_CPP" == "msvc") ]] echo "Compiler Visual Studio $version ($vs_release-$bits) has been found" echo call \""$vcvars_bat_file"\" >do-vcvars.bat echo "bash -c export > vcvars.env" >>do-vcvars.bat - ./do-vcvars.bat - source vcvars.env - rm -f do-vcvars.bat vcvars.env + python file-runner.py do-vcvars.bat + grep -v -E "(\(.*=)|(\!.*=)|([A-Z]\-[A-Z].*=)" vcvars_filtered.env + source vcvars_filtered.env + rm -f do-vcvars.bat vcvars.env vcvars_filtered.env for cpp_standard in "${MSVC_CPP_STANDARDS[@]}"; do touch empty_file.cpp cpp_output=$(cl.exe "$cpp_standard" empty_file.cpp 2>&1 || true) @@ -196,24 +204,28 @@ fi # Find /c/Programs/*/bin/g++ in case of Windows and no ARGS_CPP if [[ "$MACHINE" == "Windows" && "$ARGS_CPP" == "" ]]; then - for d in /c/Programs/*/ ; do - gpp="${d}bin/g++.exe" - gpp_output=$($gpp 2>&1 || true) - if [[ $gpp_output == *"no input files"* ]]; then - for gpp_standard in "${CPP_STANDARDS[@]}"; do - touch empty_file.cpp - gpp_output=$($gpp "$gpp_standard" empty_file.cpp 2>&1 || true) - if [[ ! $gpp_output == *"unrecognized"* && ! $gpp_output == *"standard"* ]]; then - run_tests "$gpp" "$gpp_standard" - if [[ ! "$done" == "" ]]; then - done="$done, " - fi - done="$done$gpp@$gpp_standard" - fi - rm -f empty_file.* - done + for d in /c/Programs/*/; do + dir="${d}bin" + OLD_PATH="$PATH" + export PATH="$dir":$PATH + gpp="${d}bin/g++.exe" + gpp_output=$($gpp 2>&1 || true) + if [[ $gpp_output == *"no input files"* ]]; then + for gpp_standard in "${CPP_STANDARDS[@]}"; do + touch empty_file.cpp + gpp_output=$($gpp "$gpp_standard" empty_file.cpp 2>&1 || true) + if [[ ! $gpp_output == *"unrecognized"* && ! $gpp_output == *"standard"* ]]; then + run_tests "$gpp" "$gpp_standard" + if [[ ! "$done" == "" ]]; then + done="$done, " + fi + done="$done$gpp@$gpp_standard" fi - done + rm -f empty_file.* + done + fi + export PATH="$OLD_PATH" + done fi for compiler in "${COMPILERS[@]}"; do @@ -247,11 +259,11 @@ for compiler in "${COMPILERS[@]}"; do done done -if [[ "$machine" == "Windows" ]]; then - for f in msvc-2022-include windows-kit-10.0.19041.0-include; do - rm -rf "${TESTS_DIR:?}"/lib/$f - done -fi +#if [[ "$machine" == "Windows" ]]; then +# for f in msvc-${MSVC_INCLUDE_YEAR}-include windows-kit-${WINKIT_INCLUDE_VERSION}-include; do +# rm -rf "${TESTS_DIR:?}"/lib/$f +# done +#fi if [[ -z "$done" ]]; then echo -e "${RED}[ERROR]${NC} No compilers found\n" diff --git a/tests/scripts/compile b/tests/scripts/compile index db4c04a..95344be 100644 --- a/tests/scripts/compile +++ b/tests/scripts/compile @@ -33,29 +33,26 @@ fi rm -f "$exe_file" EXTRA_ARGS="" -if [[ "$CPP" == "clang++" && "$MACHINE" == "Windows" ]]; then - msvc_version="2022" - wk_version="10.0.19041.0" - EXTRA_ARGS=" -I\"$TESTS_DIR/lib/msvc-$msvc_version-include\"" - for s in cppwinrt shared ucrt um winrt; do - EXTRA_ARGS="$EXTRA_ARGS -I\"$TESTS_DIR/lib/windows-kit-$wk_version-include/$s\"" - done -fi +#if [[ "$CPP" == "clang++" && "$MACHINE" == "Windows" ]]; then +# msvc_version="2022" +# wk_version="10.0.19041.0" +# EXTRA_ARGS=" -I\"$TESTS_DIR/lib/msvc-$msvc_version-include\"" +# for s in cppwinrt shared ucrt um winrt; do +# EXTRA_ARGS="$EXTRA_ARGS -I\"$TESTS_DIR/lib/windows-kit-$wk_version-include/$s\"" +# done +#fi if [[ "$CPP" == "cl.exe" ]]; then echo "Compiling $src_file, running:" "$CPP" "$CPP_STANDARD" "-F268435456" "-EHsc" "-O2" -I"${CPP_INCLUDE_DIR}" -Fe"$exe_file" "$src_file" - "$CPP" "$CPP_STANDARD" "-F268435456" "-EHsc" "-O2" -I"${CPP_INCLUDE_DIR}" -Fe"$exe_file" "$src_file" &>/dev/null + "$CPP" "$CPP_STANDARD" "-F268435456" "-EHsc" "-O2" -I"${CPP_INCLUDE_DIR}" -Fe"$exe_file" "$src_file" else "$CPP" -v echo "Compiling $src_file, running:" "$CPP" "$CPP_OPTS" "$CPP_STANDARD" -Wpedantic -Werror -I"${CPP_INCLUDE_DIR}""$EXTRA_ARGS" -o"$exe_file" -O2 "$src_file" dir=$(dirname "$CPP") - OLD_PATH="$PATH" if [[ "$dir" == *"/bin" ]]; then - export PATH="$dir":$PATH EXTRA_ARGS="${EXTRA_ARGS} -static" fi eval "$CPP" "$CPP_OPTS" "$CPP_STANDARD" -Wpedantic -Werror -I"${CPP_INCLUDE_DIR}""$EXTRA_ARGS" -o"$exe_file" -O2 "$src_file" - export PATH="$OLD_PATH" fi rm -f ./*.o ./*.obj diff --git a/tests/test-002_run-fcmp-wcmp/files/output.01.bom b/tests/test-002_run-fcmp-wcmp/files/output.01.bom new file mode 100644 index 0000000..7185e98 --- /dev/null +++ b/tests/test-002_run-fcmp-wcmp/files/output.01.bom @@ -0,0 +1,3 @@ +abc 01 \%test!$ + +me diff --git a/tests/test-002_run-fcmp-wcmp/run.sh b/tests/test-002_run-fcmp-wcmp/run.sh index 25c5e3c..1c311ce 100644 --- a/tests/test-002_run-fcmp-wcmp/run.sh +++ b/tests/test-002_run-fcmp-wcmp/run.sh @@ -5,6 +5,11 @@ bash ../scripts/compile src/wcmp.cpp bash ../scripts/test-ref r1 "$VALGRIND" ./wcmp files/input.01 files/output.01 files/answer.01 bash ../scripts/test-ref r2 "$VALGRIND" ./wcmp files/input.01 files/output.01 files/output.01 bash ../scripts/test-ref r3 "$VALGRIND" ./wcmp files/input.01 files/output.01 files/answer2.01 + +# BOM shouldn't change refs +bash ../scripts/test-ref r1 "$VALGRIND" ./wcmp files/input.01 files/output.01.bom files/answer.01 +bash ../scripts/test-ref r2 "$VALGRIND" ./wcmp files/input.01 files/output.01.bom files/output.01 +bash ../scripts/test-ref r3 "$VALGRIND" ./wcmp files/input.01 files/output.01.bom files/answer2.01 rm -f wcmp wcmp.exe bash ../scripts/compile src/fcmp.cpp diff --git a/tests/test-003_run-rnd/refs/r1/stdout b/tests/test-003_run-rnd/refs/r1/stdout index 16e69b2..6d1bd37 100644 --- a/tests/test-003_run-rnd/refs/r1/stdout +++ b/tests/test-003_run-rnd/refs/r1/stdout @@ -30,3 +30,24 @@ vutwaahqooeqoxzxwetlpecqiwgdbogiqqulttysyohwhzxzphvsfmnplizxoebzcvvfyppqbhxjksuz 980 96952 99843 +277.6710 +1652.0276 +1871.0491 +1771.8652 +521.1956 +461.5447 +1631 +828 +761 +266 +804 +409 +1085 +1837 +1834 +557 +119 +62 +1880 +1973 +1850 diff --git a/tests/test-003_run-rnd/refs/r2/stdout b/tests/test-003_run-rnd/refs/r2/stdout index 386f6ad..b42c781 100644 --- a/tests/test-003_run-rnd/refs/r2/stdout +++ b/tests/test-003_run-rnd/refs/r2/stdout @@ -120,3 +120,24 @@ clizwkchataumicxkohcrpqnyrjyzbjvsypznpembvkkkbyzvzckcmhbjbuopfbwbkntswhwsdfzabjg 860 99644 98400 +373.3556 +109.6260 +1999.1647 +1871.5326 +311.6935 +562.6932 +638 +730 +52 +334 +62 +983 +1686 +1538 +1911 +71 +259 +445 +1363 +1897 +1175 diff --git a/tests/test-003_run-rnd/src/gen.cpp b/tests/test-003_run-rnd/src/gen.cpp index fea739a..d1d9be3 100644 --- a/tests/test-003_run-rnd/src/gen.cpp +++ b/tests/test-003_run-rnd/src/gen.cpp @@ -102,4 +102,29 @@ int main(int argc, char* argv[]) println(rnd.wnext(1000LL, 20LL)); println(rnd.wnext(100000, 200)); println(rnd.wnext(100000LL, 200LL)); + + std::cout << std::fixed << std::setprecision(4) << rnd.next((float) 42.0, (float) 2011.1109) << std::endl; + std::cout << std::fixed << std::setprecision(4) << rnd.next((double) 42.0, (double) 2011.1109) << std::endl; + std::cout << std::fixed << std::setprecision(4) << rnd.wnext((float) 42.0, (float) 2011.1109, 5) << std::endl; + std::cout << std::fixed << std::setprecision(4) << rnd.wnext((double) 42.0, (double) 2011.1109, 5) << std::endl; + std::cout << std::fixed << std::setprecision(4) << rnd.wnext((float) 42.0, (float) 2011.1109, -7) << std::endl; + std::cout << std::fixed << std::setprecision(4) << rnd.wnext((double) 42.0, (double) 2011.1109, -7) << std::endl; + + std::cout << rnd.next((unsigned long long) 42, (unsigned long long) 2011) << std::endl; + std::cout << rnd.next((unsigned int) 42, (unsigned int) 2011) << std::endl; + std::cout << rnd.next((unsigned short) 42, (unsigned short) 2011) << std::endl; + + std::cout << rnd.wnext((unsigned long long) 42, (unsigned long long) 2011, -3) << std::endl; + std::cout << rnd.wnext((unsigned int) 42, (unsigned int) 2011, -3) << std::endl; + std::cout << rnd.wnext((unsigned short) 42, (unsigned short) 2011, -3) << std::endl; + std::cout << rnd.wnext((unsigned long long) 42, (unsigned long long) 2011, 3) << std::endl; + std::cout << rnd.wnext((unsigned int) 42, (unsigned int) 2011, 3) << std::endl; + std::cout << rnd.wnext((unsigned short) 42, (unsigned short) 2011, 3) << std::endl; + + std::cout << rnd.wnext((signed long long) 42, (signed long long) 2011, -5) << std::endl; + std::cout << rnd.wnext((signed int) 42, (signed int) 2011, -5) << std::endl; + std::cout << rnd.wnext((signed short) 42, (signed short) 2011, -5) << std::endl; + std::cout << rnd.wnext((signed long long) 42, (signed long long) 2011, 4) << std::endl; + std::cout << rnd.wnext((signed int) 42, (signed int) 2011, 4) << std::endl; + std::cout << rnd.wnext((signed short) 42, (signed short) 2011, 4) << std::endl; } diff --git a/tests/test-006_interactors/files/crossrun/CrossRun.jar b/tests/test-006_interactors/files/crossrun/CrossRun.jar new file mode 100644 index 0000000..b41a8fb Binary files /dev/null and b/tests/test-006_interactors/files/crossrun/CrossRun.jar differ diff --git a/tests/test-006_interactors/files/crossrun/CrossRun.java b/tests/test-006_interactors/files/crossrun/CrossRun.java new file mode 100644 index 0000000..c5a1019 --- /dev/null +++ b/tests/test-006_interactors/files/crossrun/CrossRun.java @@ -0,0 +1,162 @@ +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CrossRun { + private static final List messages = Collections.synchronizedList(new ArrayList()); + + private static volatile boolean failed = false; + + private static void error(String message) { + System.out.println("ERROR: " + message); + System.exit(1); + } + + public static void main(String[] args) { + int sep = -1; + for (int i = 0; i < args.length; i++) { + if (args[i].equals("--")) { + sep = i; + break; + } + } + + if (sep == -1) { + error("Expected exactly one '--' as separator for the two process command lines."); + } + + String[] params1 = new String[sep]; + System.arraycopy(args, 0, params1, 0, params1.length); + + String[] params2 = new String[args.length - sep - 1]; + System.arraycopy(args, sep + 1, params2, 0, params2.length); + + long startTime = System.currentTimeMillis(); + + try { + runProcesses(params1, params2); + } catch (IOException e) { + error(e.getMessage()); + } + + System.out.println("Completed in " + (System.currentTimeMillis() - startTime) + " ms."); + } + + private static void runProcesses(String[] params1, String[] params2) throws IOException { + Process process1 = new ProcessBuilder(params1).start(); + Process process2 = new ProcessBuilder(params2).start(); + + Thread readProcess1WriteProcess2Thread = + new Thread(new StreamProxyRunner("process1", "process2", process1.getInputStream(), process2.getOutputStream())); + Thread readProcess2WriteProcess1Thread = + new Thread(new StreamProxyRunner("process2", "process1", process2.getInputStream(), process1.getOutputStream())); + + readProcess1WriteProcess2Thread.start(); + readProcess2WriteProcess1Thread.start(); + + int processExitCode1 = -1; + try { + processExitCode1 = process1.waitFor(); + } catch (InterruptedException e) { + error(e.getMessage()); + } + + int processExitCode2 = -1; + try { + processExitCode2 = process2.waitFor(); + } catch (InterruptedException e) { + error(e.getMessage()); + } + + try { + readProcess1WriteProcess2Thread.join(); + } catch (InterruptedException e) { + error(e.getMessage()); + } + + try { + readProcess2WriteProcess1Thread.join(); + } catch (InterruptedException e) { + error(e.getMessage()); + } + + if (processExitCode1 != 0) { + messages.add("The process 1 returned with exit code " + processExitCode1 + "."); + } + + if (processExitCode2 != 0) { + messages.add("The process 2 returned with exit code " + processExitCode2 + "."); + } + + for (String message : messages) { + System.out.println("* " + message); + } + + if (failed) { + System.exit(1); + } + } + + @SuppressWarnings("ClassCanBeRecord") + private static final class StreamProxyRunner implements Runnable { + private final String processName1; + private final String processName2; + private final InputStream inputStream; + private final OutputStream outputStream; + + private StreamProxyRunner(final String processName1, + final String processName2, + final InputStream inputStream, + final OutputStream outputStream) { + this.processName1 = processName1; + this.processName2 = processName2; + this.inputStream = inputStream; + this.outputStream = outputStream; + } + + @Override + public void run() { + byte[] buffer = new byte[65536]; + + while (true) { + int size; + + try { + size = inputStream.read(buffer); + } catch (IOException e) { + messages.add("Unexpected exception " + e.getClass().getSimpleName() + " while reading from the output of the " + processName1 + " process: " + e.getMessage()); + failed = true; + break; + } + + if (size < 0) { + break; + } + + try { + outputStream.write(buffer, 0, size); + outputStream.flush(); + } catch (IOException e) { + messages.add("Unexpected exception " + e.getClass().getSimpleName() + " while writing to the input of the " + processName2 + " process: " + e.getMessage()); + failed = true; + break; + } + } + + try { + inputStream.close(); + } catch (IOException e) { + // No operations. + } + + try { + outputStream.close(); + } catch (IOException e) { + // No operations. + } + } + } +} diff --git a/tests/test-006_interactors/files/crossrun/build-cross-run.sh b/tests/test-006_interactors/files/crossrun/build-cross-run.sh new file mode 100644 index 0000000..da01e8e --- /dev/null +++ b/tests/test-006_interactors/files/crossrun/build-cross-run.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +uname_output="$(uname -s)" +case "${uname_output}" in +Linux*) machine=Linux ;; +Darwin*) machine=Mac ;; +CYGWIN*) machine=Windows ;; +MINGW*) machine=Windows ;; +MSYS*) machine=Windows ;; +*) echo "Unknown system '${uname_output}'" && exit 1 ;; +esac + +# If JAVA8_32_HOME is set, use its javac +if [[ -n "$JAVA8_32_HOME" ]]; then + if [[ "$machine" == "Windows" ]]; then + JAVA8_32_HOME=$(cygpath "$JAVA8_32_HOME") + fi + export PATH="$JAVA8_32_HOME/bin:$PATH" +fi + +# If JAVA7_32_HOME is set, use its javac +if [[ -n "$JAVA7_32_HOME" ]]; then + if [[ "$machine" == "Windows" ]]; then + JAVA7_32_HOME=$(cygpath "$JAVA7_32_HOME") + fi + export PATH="$JAVA7_32_HOME/bin:$PATH" +fi + +echo $PATH + +# Show javac version +javac -version + +# Compile all .java files +javac *.java + +# Check if the compilation was successful +if [ $? -ne 0 ]; then + echo "Error during compilation. Exiting." + exit 1 +fi + +# Create a manifest file for the jar +echo "Main-Class: CrossRun" > manifest.mf + +# Package all .class files into a jar +jar cvfm CrossRun.jar manifest.mf *.class + +# Cleanup .class files and manifest file (optional) +rm *.class +rm manifest.mf + +echo "Done. CrossRun.jar created." diff --git a/tests/test-006_interactors/files/unix/input.01 b/tests/test-006_interactors/files/unix/input.01 new file mode 100644 index 0000000..95ea5f7 --- /dev/null +++ b/tests/test-006_interactors/files/unix/input.01 @@ -0,0 +1,4 @@ +3 +1 2 +3 4 +-1 -1 diff --git a/tests/test-006_interactors/files/unix/participant.01 b/tests/test-006_interactors/files/unix/participant.01 new file mode 100644 index 0000000..e64e44e --- /dev/null +++ b/tests/test-006_interactors/files/unix/participant.01 @@ -0,0 +1,3 @@ +5 +6 +7 diff --git a/tests/test-006_interactors/files/win/input.01 b/tests/test-006_interactors/files/win/input.01 new file mode 100644 index 0000000..8924ce7 --- /dev/null +++ b/tests/test-006_interactors/files/win/input.01 @@ -0,0 +1,4 @@ +3 +1 2 +3 4 +-1 -1 diff --git a/tests/test-006_interactors/files/win/participant.01 b/tests/test-006_interactors/files/win/participant.01 new file mode 100644 index 0000000..fb5f06a --- /dev/null +++ b/tests/test-006_interactors/files/win/participant.01 @@ -0,0 +1,3 @@ +5 +6 +7 diff --git a/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-1/exit_code b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-1/exit_code new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-1/exit_code @@ -0,0 +1 @@ +0 diff --git a/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-1/stderr b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-1/stderr new file mode 100644 index 0000000..029cfd4 --- /dev/null +++ b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-1/stderr @@ -0,0 +1 @@ +ok 3 queries processed diff --git a/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-1/stdout b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-1/stdout new file mode 100644 index 0000000..05dd8d6 --- /dev/null +++ b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-1/stdout @@ -0,0 +1,3 @@ +1 2 +3 4 +-1 -1 diff --git a/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-2/exit_code b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-2/exit_code new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-2/exit_code @@ -0,0 +1 @@ +0 diff --git a/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-2/stderr b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-2/stderr new file mode 100644 index 0000000..e69de29 diff --git a/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-2/stdout b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-2/stdout new file mode 100644 index 0000000..e64e44e --- /dev/null +++ b/tests/test-006_interactors/refs/r-interactor-a-plus-b-1-2/stdout @@ -0,0 +1,3 @@ +5 +6 +7 diff --git a/tests/test-006_interactors/refs/r-interactor-a-plus-b-2-1/exit_code b/tests/test-006_interactors/refs/r-interactor-a-plus-b-2-1/exit_code new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/test-006_interactors/refs/r-interactor-a-plus-b-2-1/exit_code @@ -0,0 +1 @@ +0 diff --git a/tests/test-006_interactors/refs/r-interactor-a-plus-b-2-1/stderr b/tests/test-006_interactors/refs/r-interactor-a-plus-b-2-1/stderr new file mode 100644 index 0000000..e69de29 diff --git a/tests/test-006_interactors/refs/r-interactor-a-plus-b-2-1/stdout b/tests/test-006_interactors/refs/r-interactor-a-plus-b-2-1/stdout new file mode 100644 index 0000000..7daa778 --- /dev/null +++ b/tests/test-006_interactors/refs/r-interactor-a-plus-b-2-1/stdout @@ -0,0 +1,3 @@ +3 +7 +-2 diff --git a/tests/test-006_interactors/run.sh b/tests/test-006_interactors/run.sh new file mode 100644 index 0000000..7d18216 --- /dev/null +++ b/tests/test-006_interactors/run.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eo pipefail + +os="unix" +if [[ "$MACHINE" == "Windows" ]]; then + os="win" +fi + +bash ../scripts/compile src/interactor-a-plus-b.cpp +bash ../scripts/test-ref r-interactor-a-plus-b-1-1 "$VALGRIND" ./interactor-a-plus-b files/"$os"/input.01 output.01 < files/"$os"/participant.01 +tr -d '\r' < output.01 > output.01.nix +bash ../scripts/test-ref r-interactor-a-plus-b-1-2 cat output.01.nix +rm -f output.01 output.01.nix + +bash ../scripts/compile src/interactive-a-plus-b.cpp +echo "Running 'java -jar files/crossrun/CrossRun.jar ./interactor-a-plus-b files/"$os"/input.01 output.02 -- ./interactive-a-plus-b'" +java -jar files/crossrun/CrossRun.jar ./interactor-a-plus-b files/"$os"/input.01 output.02 -- ./interactive-a-plus-b +tr -d '\r' < output.02 > output.02.nix +bash ../scripts/test-ref r-interactor-a-plus-b-2-1 cat output.02.nix +rm -f output.02 output.02.nix interactive-a-plus-b interactive-a-plus-b.exe interactor-a-plus-b interactor-a-plus-b.exe interactive_runner.out interactive_runner.err diff --git a/tests/test-006_interactors/src/interactive-a-plus-b.cpp b/tests/test-006_interactors/src/interactive-a-plus-b.cpp new file mode 100644 index 0000000..68c0ad8 --- /dev/null +++ b/tests/test-006_interactors/src/interactive-a-plus-b.cpp @@ -0,0 +1,9 @@ +#include + +using namespace std; + +int main() { + int a, b; + while (cin >> a >> b) + cout << a + b << endl; +} diff --git a/tests/test-006_interactors/src/interactive_runner.py b/tests/test-006_interactors/src/interactive_runner.py new file mode 100644 index 0000000..c06bdba --- /dev/null +++ b/tests/test-006_interactors/src/interactive_runner.py @@ -0,0 +1,122 @@ +# This code can be run as python2 or python3 in most systems. +# +# This is a small program that runs two processes, connecting the stdin of each +# one to the stdout of the other. +# It doesn't perform a lot of checking, so many errors may +# be caught internally by Python (e.g., if your command line has incorrect +# syntax) or not caught at all (e.g., if the judge or solution hangs). +# +# Run this as: +# python interactive_runner.py -- +# +# For example, if you have a testing_tool.py in python3 (that takes a single +# integer as a command line parameter) to use as judge -- like one +# downloaded from a problem statement -- and you would run your solution +# in a standalone using one of the following: +# 1. python3 my_solution.py +# 2. ./my_solution +# 3. java Solution +# 4. my_solution.exe +# Then you could run the judge and solution together, using this, as: +# 1. python interactive_runner.py python3 testing_tool.py 0 -- python3 my_solution.py +# 2. python interactive_runner.py python3 testing_tool.py 0 -- ./my_solution +# 3. python interactive_runner.py python3 testing_tool.py 0 -- java solution +# 4. python interactive_runner.py python3 testing_tool.py 0 -- my_solution.exe +# Notice that the solution in cases 2, 3 and 4 would usually have a +# compilation step before running, which you should run in your usual way +# before using this tool. +# +# This is only intended as a convenient tool to help contestants test solutions +# locally. In particular, it is not identical to the implementation on our +# server, which is more complex. +# +# The standard streams are handled the following way: +# - judge's stdin is connected to the solution's stdout; +# - judge's stdout is connected to the solution's stdin; +# - stderrs of both judge and solution are piped to standard error stream, with +# lines prepended by "judge: " or "sol: " respectively (note, no +# synchronization is done so it's possible for the messages from both programs +# to overlap with each other). + +from __future__ import print_function +import sys, subprocess, threading + +class SubprocessThread(threading.Thread): + def __init__(self, + args, + stdin_pipe=subprocess.PIPE, + stdout_pipe=subprocess.PIPE, + stderr_prefix=None): + threading.Thread.__init__(self) + self.stderr_prefix = stderr_prefix + self.p = subprocess.Popen( + args, stdin=stdin_pipe, stdout=stdout_pipe, stderr=subprocess.PIPE) + + def run(self): + try: + self.pipeToStdErr(self.p.stderr) + self.return_code = self.p.wait() + self.error_message = None + except (SystemError, OSError): + self.return_code = -1 + self.error_message = "The process crashed or produced too much output." + + # Reads bytes from the stream and writes them to sys.stderr prepending lines + # with self.stderr_prefix. + # We are not reading by lines to guard against the case when EOL is never + # found in the stream. + def pipeToStdErr(self, stream): + new_line = True + while True: + chunk = stream.readline(1) + if not chunk: + return + chunk = chunk.decode("UTF-8") + if new_line and self.stderr_prefix: + chunk = self.stderr_prefix + chunk + new_line = False + sys.stderr.write(chunk) + if chunk.endswith("\n"): + new_line = True + sys.stderr.flush() + + +assert sys.argv.count("--") == 1, ( + "There should be exactly one instance of '--' in the command line.") +sep_index = sys.argv.index("--") +judge_args = sys.argv[1:sep_index] +sol_args = sys.argv[sep_index + 1:] + +t_sol = SubprocessThread(sol_args, stderr_prefix=" sol: ") +t_judge = SubprocessThread( + judge_args, + stdin_pipe=t_sol.p.stdout, + stdout_pipe=t_sol.p.stdin, + stderr_prefix="judge: ") +t_sol.start() +t_judge.start() +t_sol.join() +t_judge.join() + +# Print an empty line to handle the case when stderr doesn't print EOL. +print() +print("Judge return code:", t_judge.return_code) +if t_judge.error_message: + print("Judge error message:", t_judge.error_message) + +print("Solution return code:", t_sol.return_code) +if t_sol.error_message: + print("Solution error message:", t_sol.error_message) + +if t_sol.return_code: + print("A solution finishing with exit code other than 0 (without exceeding " + "time or memory limits) would be interpreted as a Runtime Error " + "in the system.") +elif t_judge.return_code: + print("A solution finishing with exit code 0 (without exceeding time or " + "memory limits) and a judge finishing with exit code other than 0 " + "would be interpreted as a Wrong Answer in the system.") +else: + print("A solution and judge both finishing with exit code 0 (without " + "exceeding time or memory limits) would be interpreted as Correct " + "in the system.") diff --git a/tests/test-006_interactors/src/interactor-a-plus-b.cpp b/tests/test-006_interactors/src/interactor-a-plus-b.cpp new file mode 100644 index 0000000..e19e148 --- /dev/null +++ b/tests/test-006_interactors/src/interactor-a-plus-b.cpp @@ -0,0 +1,26 @@ +#include "testlib.h" +#include + +using namespace std; + +int main(int argc, char *argv[]) { + setName("Interactor A+B"); + registerInteraction(argc, argv); + + // reads number of queries from test (input) file + int n = inf.readInt(); + for (int i = 0; i < n; i++) { + // reads query from test (input) file + int a = inf.readInt(); + int b = inf.readInt(); + + // writes query to the solution, endl makes flush + cout << a << " " << b << endl; + + // writes output file to be verified by checker later + tout << ouf.readInt() << endl; + } + + // just message + quitf(_ok, "%d queries processed", n); +}