diff --git a/cmscontrib/loaders/polygon.py b/cmscontrib/loaders/polygon.py index 29030fc994..ee2cbc795d 100644 --- a/cmscontrib/loaders/polygon.py +++ b/cmscontrib/loaders/polygon.py @@ -32,6 +32,7 @@ from cms import config from cms.db import Contest, User, Task, Statement, Dataset, Manager, Testcase +from cms.grading.languagemanager import HEADER_EXTS, LANGUAGES from cmscommon.crypto import build_password from cmscontrib import touch from .base_loader import ContestLoader, TaskLoader, UserLoader, LANGUAGE_MAP @@ -44,6 +45,16 @@ def make_timedelta(t): return timedelta(seconds=t) +def _format_test_path(pattern, test_number): + """Format a Polygon test path pattern for a one-based test number.""" + try: + return pattern % test_number + except (TypeError, ValueError) as error: + raise ValueError( + "Invalid Polygon test path pattern %r: %s" % (pattern, error) + ) from error + + class PolygonTaskLoader(TaskLoader): """Load a task stored using the Codeforces Polygon format. @@ -106,6 +117,20 @@ def get_task(self, get_statement=True): tree = ET.parse(os.path.join(self.path, "problem.xml")) root = tree.getroot() + # A Polygon interactor speaks the Polygon file/stdio protocol. CMS + # interactive controllers use a different START_SOLUTION protocol and + # cannot run this binary as a normal batch checker or manager. + interactor_paths = ( + os.path.join(self.path, "files", "interactor.cpp"), + os.path.join(self.path, "interactor.cpp"), + ) + if any(os.path.exists(path) for path in interactor_paths): + logger.critical( + "Polygon interactors are not supported by the Polygon loader " + "(task %s). Convert the interactor to a CMS controller.", name + ) + return None + args["name"] = name args["title"] = str(root.find('names').find("name").attrib['value']) @@ -177,6 +202,48 @@ def get_task(self, get_statement=True): infile_param = judging.attrib['input-file'] outfile_param = judging.attrib['output-file'] + # Polygon batch graders are source files compiled together with + # the contestant's submission. CMS expects these as managers + # named grader. (for example, grader.cpp). Polygon + # packages commonly put them in files/, but accept the package + # root as well. + compilation_param = "alone" + package_files = [self.path, os.path.join(self.path, "files")] + for language in LANGUAGES: + grader_filename = "grader%s" % language.source_extension + grader_src = next( + (os.path.join(directory, grader_filename) + for directory in package_files + if os.path.isfile(os.path.join(directory, grader_filename))), + None) + if grader_src is None: + continue + logger.info("Batch grader found, importing %s", grader_src) + grader_digest = self.file_cacher.put_file_from_path( + grader_src, "Grader for task %s and language %s" % + (name, language.name)) + args["managers"][grader_filename] = Manager( + grader_filename, grader_digest) + compilation_param = "grader" + + # Graders may include package-provided headers (for example, + # a participant API header). Import only direct files so the + # manager filename remains valid in the compilation sandbox. + for directory in package_files: + if not os.path.isdir(directory): + continue + for filename in os.listdir(directory): + if not any(filename.endswith(extension) + for extension in HEADER_EXTS): + continue + header_src = os.path.join(directory, filename) + if not os.path.isfile(header_src): + continue + header_digest = self.file_cacher.put_file_from_path( + header_src, + "Header manager %s for task %s" % (filename, name)) + args["managers"][filename] = Manager(filename, header_digest) + # Checker can be in any of these two locations. checker_src = os.path.join(self.path, "files", "check.cpp") if not os.path.exists(checker_src): @@ -211,14 +278,37 @@ def get_task(self, get_statement=True): evaluation_param = "diff" args["task_type"] = "Batch" - args["task_type_parameters"] = \ - ["alone", [infile_param, outfile_param], evaluation_param] + args["task_type_parameters"] = [ + compilation_param, + [infile_param, outfile_param], + evaluation_param, + ] args["score_type"] = "Sum" total_value = 100.0 input_value = 0.0 - testcases = int(testset.find('test-count').text) + test_count = testset.findtext('test-count') + if test_count is None: + logger.critical( + "Testset %s for task %s has no test-count.", + testset_name, name) + return None + try: + testcases = int(test_count) + except ValueError: + logger.critical( + "Testset %s for task %s has an invalid test-count %r.", + testset_name, name, test_count) + return None + + input_pattern = testset.findtext('input-path-pattern') + answer_pattern = testset.findtext('answer-path-pattern') + if input_pattern is None or answer_pattern is None: + logger.critical( + "Testset %s for task %s has no input/answer path pattern.", + testset_name, name) + return None n_input = testcases if n_input != 0: @@ -228,10 +318,21 @@ def get_task(self, get_statement=True): args["testcases"] = {} for i in range(testcases): - infile = os.path.join(self.path, testset_name, - "%02d" % (i + 1)) - outfile = os.path.join(self.path, testset_name, - "%02d.a" % (i + 1)) + try: + infile = os.path.join( + self.path, _format_test_path(input_pattern, i + 1)) + outfile = os.path.join( + self.path, _format_test_path(answer_pattern, i + 1)) + except ValueError as error: + logger.critical("%s", error) + return None + missing = [path for path in (infile, outfile) + if not os.path.isfile(path)] + if missing: + logger.critical( + "Testset %s for task %s references missing file(s): %s", + testset_name, name, ", ".join(missing)) + return None if self.dos2unix_found: os.system('dos2unix -q %s' % (infile, )) os.system('dos2unix -q %s' % (outfile, )) diff --git a/cmscontrib/loaders/polygon/testlib-cms.patch b/cmscontrib/loaders/polygon/testlib-cms.patch index cd61fdd8cd..06c53f31bd 100644 --- a/cmscontrib/loaders/polygon/testlib-cms.patch +++ b/cmscontrib/loaders/polygon/testlib-cms.patch @@ -1,29 +1,23 @@ ---- testlib.h.orig 2018-12-03 23:31:33.532335025 +0200 -+++ testlib.h 2018-12-04 19:00:42.933212959 +0200 -@@ -41,6 +41,22 @@ - * - */ +--- testlib.h.orig ++++ testlib.h +@@ -27,6 +27,16 @@ + + #define VERSION "0.9.45" +/* -+ * Artem Iglikov -+ * Alexander Kernozhitsky -+ * Andrey Vihrov -+ * + * Modifications for Contest Management System (CMS) support: + * - Write checker outcome to stdout and message to stderr + * - Use special localizable message strings by default -+ * - Adjust checker exit code -+ * - Adjust checker argument order (except in help and comments) -+ * - Add the "CMS" conditional macro for CMS checker format -+ * - Add the "CMS_VERBOSE_FEEDBACK" conditional macro to enable -+ * testlib-style messages -+ * - Interactors are not supported ++ * - Adjust checker exit code and argument order ++ * - Add the CMS conditional macro for checker format ++ * - Add CMS_VERBOSE_FEEDBACK to opt into testlib-style messages ++ * - Interactors remain unsupported by this checker adapter + */ + - /* NOTE: This file contains testlib library for C++. + /* + * Mike Mirzayanov * - * Check, using testlib running format: -@@ -2468,6 +2484,57 @@ +@@ -3102,6 +3112,59 @@ NORETURN void InStream::quit(TResult result, const char *msg) { int pctype = result - _partially; bool isPartial = false; @@ -40,7 +34,7 @@ +# ifndef CMS_VERBOSE_FEEDBACK +# define CMS_MSG(code, text) "translate:" code "\n" +# else -+# define CMS_MSG(code, text) text " %s\n", msg ++# define CMS_MSG(code, text) text " %s\n", message.c_str() +# endif + + if (result == _ok) { @@ -56,53 +50,59 @@ + std::fprintf(stdout, "0.0\n"); + std::fprintf(stderr, CMS_MSG(CMS_WRONG, "Wrong Output Format")); + } else if (result == _points) { -+ std::string stringPoints(removeDoubleTrailingZeroes( -+ format("%.10f", __testlib_points))); ++ char pointsBuffer[64]; ++ std::snprintf(pointsBuffer, sizeof(pointsBuffer), "%.10f", ++ __testlib_points); ++ std::string stringPoints(removeDoubleTrailingZeroes(pointsBuffer)); + std::fprintf(stdout, "%s\n", stringPoints.c_str()); + std::fprintf(stderr, CMS_MSG(CMS_PARTIAL, "Partial Score")); + } else if (result == _unexpected_eof) { + std::fprintf(stdout, "0.0\n"); + std::fprintf(stderr, CMS_MSG(CMS_WRONG, "Unexpected EOF")); + } else if (result >= _partially) { -+ double score = (double)pctype / 200.0; ++ double score = (double) pctype / 200.0; + std::fprintf(stdout, "%.3f\n", score); + std::fprintf(stderr, CMS_MSG(CMS_PARTIAL, "Partial Score")); + } else if (result == _fail) { -+ std::fprintf(stderr, "FAIL %s\n", msg); ++ std::fprintf(stderr, "FAIL %s\n", message.c_str()); + halt(1); + } else { -+ std::fprintf(stderr, "FAIL unknown result %d\n", (int)result); ++ std::fprintf(stderr, "FAIL unknown result %d\n", (int) result); + halt(1); + } + + halt(0); +#endif + - switch (result) - { - case _ok: -@@ -3922,6 +3989,10 @@ + switch (result) { + case _ok: + errorName = "ok "; +@@ -4667,6 +4730,12 @@ void setAppesModeEncoding(std::string appesModeEncoding) { + } - void registerInteraction(int argc, char* argv[]) - { + void registerInteraction(int argc, char *argv[]) { +#ifdef CMS -+ quit(_fail, "Interactors are not supported"); ++ TestlibFinalizeGuard::registered = true; ++ std::fprintf(stderr, "FAIL Interactors are not supported\n"); ++ halt(1); +#endif + __testlib_ensuresPreconditions(); + __testlib_set_testset_and_group(argc, argv); + TestlibFinalizeGuard::registered = true; +@@ -4899,9 +4968,14 @@ void registerTestlibCmd(int argc, char *argv[]) { + #endif - testlibMode = _interactor; -@@ -4081,8 +4152,13 @@ - } - - inf.init(argv[1], _input); + inf.init(args[1], _input); +#ifdef CMS -+ ouf.init(argv[3], _output); -+ ans.init(argv[2], _answer); ++ ouf.init(args[3], _output); ++ ans.init(args[2], _answer); +#else - ouf.init(argv[2], _output); - ans.init(argv[3], _answer); + ouf.init(args[2], _output); +- ouf.skipBom(); + ans.init(args[3], _answer); +#endif ++ ouf.skipBom(); } - void registerTestlib(int argc, ...) + void registerTestlib(int argc, ...) { diff --git a/cmscontrib/loaders/polygon/testlib.h b/cmscontrib/loaders/polygon/testlib.h index 612a439a9d..48615b8a51 100644 --- a/cmscontrib/loaders/polygon/testlib.h +++ b/cmscontrib/loaders/polygon/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 usefull for you: - * rnd.next(); rnd.next(100); rnd.next(1, 2); + * generators. Probably, these sample calls will be useful for you: + * 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,28 @@ #define _TESTLIB_H_ /* - * Copyright (c) 2005-2018 + * Copyright (c) 2005-2025 */ -#define VERSION "0.9.21" +#define VERSION "0.9.45" + +/* + * Modifications for Contest Management System (CMS) support: + * - Write checker outcome to stdout and message to stderr + * - Use special localizable message strings by default + * - Adjust checker exit code and argument order + * - Add the CMS conditional macro for checker format + * - Add CMS_VERBOSE_FEEDBACK to opt into testlib-style messages + * - Interactors remain unsupported by this checker adapter + */ -/* +/* * 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 @@ -41,118 +51,122 @@ * */ -/* - * Artem Iglikov - * Alexander Kernozhitsky - * Andrey Vihrov - * - * Modifications for Contest Management System (CMS) support: - * - Write checker outcome to stdout and message to stderr - * - Use special localizable message strings by default - * - Adjust checker exit code - * - Adjust checker argument order (except in help and comments) - * - Add the "CMS" conditional macro for CMS checker format - * - Add the "CMS_VERBOSE_FEEDBACK" conditional macro to enable - * testlib-style messages - * - Interactors are not supported - * - * Backports: - * - 1bcfacc3a97667b38a3aef9d95b97edc6a9db688 (MikeMirzayanov/testlib#79) - */ - /* NOTE: This file contains testlib library for C++. * * Check, using testlib running format: * 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), * writes output to program via stdout (use cout, printf, etc). */ -const char* latestFeatures[] = { - "Fixed issue #79: fixed missed guard against repeated header include", - "Fixed stringstream repeated usage issue", - "Fixed compilation in g++ (for std=c++03)", - "Batch of println functions (support collections, iterator ranges)", - "Introduced rnd.perm(size, first = 0) to generate a `first`-indexed permutation", - "Allow any whitespace in readInts-like functions for non-validators", - "Ignore 4+ command line arguments ifdef EJUDGE", - "Speed up of vtos", - "Show line number in validators in case of incorrect format", - "Truncate huge checker/validator/interactor message", - "Fixed issue with readTokenTo of very long tokens, now aborts with _pe/_fail depending of a stream type", - "Introduced InStream::ensure/ensuref checking a condition, returns wa/fail depending of a stream type", - "Fixed compilation in VS 2015+", - "Introduced space-separated read functions: readWords/readTokens, multilines read functions: readStrings/readLines", - "Introduced space-separated read functions: readInts/readIntegers/readLongs/readUnsignedLongs/readDoubles/readReals/readStrictDoubles/readStrictReals", - "Introduced split/tokenize functions to separate string by given char", - "Introduced InStream::readUnsignedLong and InStream::readLong with unsigned long long paramerters", - "Supported --testOverviewLogFileName for validator: bounds hits + features", - "Fixed UB (sequence points) in random_t", - "POINTS_EXIT_CODE returned back to 7 (instead of 0)", - "Removed disable buffers for interactive problems, because it works unexpectedly in wine", - "InStream over string: constructor of InStream from base InStream to inherit policies and std::string", - "Added expectedButFound quit function, examples: expectedButFound(_wa, 10, 20), expectedButFound(_fail, ja, pa, \"[n=%d,m=%d]\", n, m)", - "Fixed incorrect interval parsing in patterns", - "Use registerGen(argc, argv, 1) to develop new generator, use registerGen(argc, argv, 0) to compile old generators (originally created for testlib under 0.8.7)", - "Introduced disableFinalizeGuard() to switch off finalization checkings", - "Use join() functions to format a range of items as a single string (separated by spaces or other separators)", - "Use -DENABLE_UNEXPECTED_EOF to enable special exit code (by default, 8) in case of unexpected eof. It is good idea to use it in interactors", - "Use -DUSE_RND_AS_BEFORE_087 to compile in compatibility mode with random behavior of versions before 0.8.7", - "Fixed bug with nan in stringToDouble", - "Fixed issue around overloads for size_t on x64", - "Added attribute 'points' to the XML output in case of result=_points", - "Exit codes can be customized via macros, e.g. -DPE_EXIT_CODE=14", - "Introduced InStream function readWordTo/readTokenTo/readStringTo/readLineTo for faster reading", - "Introduced global functions: format(), englishEnding(), upperCase(), lowerCase(), compress()", - "Manual buffer in InStreams, some IO speed improvements", - "Introduced quitif(bool, const char* pattern, ...) which delegates to quitf() in case of first argument is true", - "Introduced guard against missed quitf() in checker or readEof() in validators", - "Supported readStrictReal/readStrictDouble - to use in validators to check strictly float numbers", - "Supported registerInteraction(argc, argv)", - "Print checker message to the stderr instead of stdout", - "Supported TResult _points to output calculated score, use quitp(...) functions", - "Fixed to be compilable on Mac", - "PC_BASE_EXIT_CODE=50 in case of defined TESTSYS", - "Fixed issues 19-21, added __attribute__ format printf", - "Some bug fixes", - "ouf.readInt(1, 100) and similar calls return WA", - "Modified random_t to avoid integer overflow", - "Truncated checker output [patch by Stepan Gatilov]", - "Renamed class random -> class random_t", - "Supported name parameter for read-and-validation methods, like readInt(1, 2, \"n\")", - "Fixed bug in readDouble()", - "Improved ensuref(), fixed nextLine to work in case of EOF, added startTest()", - "Supported \"partially correct\", example: quitf(_pc(13), \"result=%d\", result)", - "Added shuffle(begin, end), use it instead of random_shuffle(begin, end)", - "Added readLine(const string& ptrn), fixed the logic of readLine() in the validation mode", - "Package extended with samples of generators and validators", - "Written the documentation for classes and public methods in testlib.h", - "Implemented random routine to support generators, use registerGen() to switch it on", - "Implemented strict mode to validate tests, use registerValidation() to switch it on", - "Now ncmp.cpp and wcmp.cpp are return WA if answer is suffix or prefix of the output", - "Added InStream::readLong() and removed InStream::readLongint()", - "Now no footer added to each report by default (use directive FOOTER to switch on)", - "Now every checker has a name, use setName(const char* format, ...) to set it", - "Now it is compatible with TTS (by Kittens Computing)", - "Added \'ensure(condition, message = \"\")\' feature, it works like assert()", - "Fixed compatibility with MS C++ 7.1", - "Added footer with exit code information", - "Added compatibility with EJUDGE (compile with EJUDGE directive)", - "Added compatibility with Contester (compile with CONTESTER directive)" - }; +const char *latestFeatures[] = { + "Remove incorrect const attributes", + "Added ConstantBoundsLog, VariablesLog to validator testOverviewLogFile", + "Use setAppesModeEncoding to change xml encoding from windows-1251 to other", + "rnd.any/wany use distance/advance instead of -/+: now they support sets/multisets", + "Use syntax `int t = inf.readInt(1, 3, \"~t\");` to skip the lower bound check. Tildes can be used on either side or both: ~t, t~, ~t~", + "Supported EJUDGE support in registerTestlibCmd", + "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)", + "Fixed issue #87: readStrictDouble accepts \"-0.00\"", + "Fixed issue #83: added InStream::quitif(condition, ...)", + "Fixed issue #79: fixed missed guard against repeated header include", + "Fixed issue #80: fixed UB in case of huge quitf message", + "Fixed issue #84: added readXs(size, indexBase = 1)", + "Fixed stringstream repeated usage issue", + "Fixed compilation in g++ (for std=c++03)", + "Batch of println functions (support collections, iterator ranges)", + "Introduced rnd.perm(size, first = 0) to generate a `first`-indexed permutation", + "Allow any whitespace in readInts-like functions for non-validators", + "Ignore 4+ command line arguments ifdef EJUDGE", + "Speed up of vtos", + "Show line number in validators in case of incorrect format", + "Truncate huge checker/validator/interactor message", + "Fixed issue with readTokenTo of very long tokens, now aborts with _pe/_fail depending of a stream type", + "Introduced InStream::ensure/ensuref checking a condition, returns wa/fail depending of a stream type", + "Fixed compilation in VS 2015+", + "Introduced space-separated read functions: readWords/readTokens, multilines read functions: readStrings/readLines", + "Introduced space-separated read functions: readInts/readIntegers/readLongs/readUnsignedLongs/readDoubles/readReals/readStrictDoubles/readStrictReals", + "Introduced split/tokenize functions to separate string by given char", + "Introduced InStream::readUnsignedLong and InStream::readLong with unsigned long long parameters", + "Supported --testOverviewLogFileName for validator: bounds hits + features", + "Fixed UB (sequence points) in random_t", + "POINTS_EXIT_CODE returned back to 7 (instead of 0)", + "Removed disable buffers for interactive problems, because it works unexpectedly in wine", + "InStream over string: constructor of InStream from base InStream to inherit policies and std::string", + "Added expectedButFound quit function, examples: expectedButFound(_wa, 10, 20), expectedButFound(_fail, ja, pa, \"[n=%d,m=%d]\", n, m)", + "Fixed incorrect interval parsing in patterns", + "Use registerGen(argc, argv, 1) to develop new generator, use registerGen(argc, argv, 0) to compile old generators (originally created for testlib under 0.8.7)", + "Introduced disableFinalizeGuard() to switch off finalization checkings", + "Use join() functions to format a range of items as a single string (separated by spaces or other separators)", + "Use -DENABLE_UNEXPECTED_EOF to enable special exit code (by default, 8) in case of unexpected eof. It is good idea to use it in interactors", + "Use -DUSE_RND_AS_BEFORE_087 to compile in compatibility mode with random behavior of versions before 0.8.7", + "Fixed bug with nan in stringToDouble", + "Fixed issue around overloads for size_t on x64", + "Added attribute 'points' to the XML output in case of result=_points", + "Exit codes can be customized via macros, e.g. -DPE_EXIT_CODE=14", + "Introduced InStream function readWordTo/readTokenTo/readStringTo/readLineTo for faster reading", + "Introduced global functions: format(), englishEnding(), upperCase(), lowerCase(), compress()", + "Manual buffer in InStreams, some IO speed improvements", + "Introduced quitif(bool, const char* pattern, ...) which delegates to quitf() in case of first argument is true", + "Introduced guard against missed quitf() in checker or readEof() in validators", + "Supported readStrictReal/readStrictDouble - to use in validators to check strictly float numbers", + "Supported registerInteraction(argc, argv)", + "Print checker message to the stderr instead of stdout", + "Supported TResult _points to output calculated score, use quitp(...) functions", + "Fixed to be compilable on Mac", + "PC_BASE_EXIT_CODE=50 in case of defined TESTSYS", + "Fixed issues 19-21, added __attribute__ format printf", + "Some bug fixes", + "ouf.readInt(1, 100) and similar calls return WA", + "Modified random_t to avoid integer overflow", + "Truncated checker output [patch by Stepan Gatilov]", + "Renamed class random -> class random_t", + "Supported name parameter for read-and-validation methods, like readInt(1, 2, \"n\")", + "Fixed bug in readDouble()", + "Improved ensuref(), fixed nextLine to work in case of EOF, added startTest()", + "Supported \"partially correct\", example: quitf(_pc(13), \"result=%d\", result)", + "Added shuffle(begin, end), use it instead of random_shuffle(begin, end)", + "Added readLine(const string& ptrn), fixed the logic of readLine() in the validation mode", + "Package extended with samples of generators and validators", + "Written the documentation for classes and public methods in testlib.h", + "Implemented random routine to support generators, use registerGen() to switch it on", + "Implemented strict mode to validate tests, use registerValidation() to switch it on", + "Now ncmp.cpp and wcmp.cpp are return WA if answer is suffix or prefix of the output", + "Added InStream::readLong() and removed InStream::readLongint()", + "Now no footer added to each report by default (use directive FOOTER to switch on)", + "Now every checker has a name, use setName(const char* format, ...) to set it", + "Now it is compatible with TTS (by Kittens Computing)", + "Added \'ensure(condition, message = \"\")\' feature, it works like assert()", + "Fixed compatibility with MS C++ 7.1", + "Added footer with exit code information", + "Added compatibility with EJUDGE (compile with EJUDGE directive)", + "Added compatibility with Contester (compile with CONTESTER directive)" +}; #ifdef _MSC_VER #define _CRT_SECURE_NO_DEPRECATE @@ -175,6 +189,7 @@ const char* latestFeatures[] = { #include #include #include +#include #include #include #include @@ -182,9 +197,15 @@ 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 !defined(_MSC_VER) || _MSC_VER>1400 +#if (_WIN32 || __WIN32__ || __WIN32 || _WIN64 || __WIN64__ || __WIN64 || WINNT || __WINNT || __WINNT__ || __CYGWIN__) +# if !defined(_MSC_VER) || _MSC_VER > 1400 # define NOMINMAX 1 # include # else @@ -193,7 +214,7 @@ const char* latestFeatures[] = { # endif # include # define ON_WINDOWS -# if defined(_MSC_VER) && _MSC_VER>1400 +# if defined(_MSC_VER) && _MSC_VER > 1400 # pragma warning( disable : 4127 ) # pragma warning( disable : 4146 ) # pragma warning( disable : 4458 ) @@ -304,7 +325,7 @@ const char* latestFeatures[] = { #else # define NORETURN #endif - + static char __testlib_format_buffer[16777216]; static int __testlib_format_buffer_usage_count = 0; @@ -320,30 +341,72 @@ static int __testlib_format_buffer_usage_count = 0; result = std::string(__testlib_format_buffer); \ __testlib_format_buffer_usage_count--; \ +#ifdef __GNUC__ +__attribute__ ((format (printf, 1, 2))) +#endif +std::string testlib_format_(const char *fmt, ...); +std::string testlib_format_(const std::string fmt, ...); + const long long __TESTLIB_LONGLONG_MAX = 9223372036854775807LL; +const int __TESTLIB_MAX_TEST_CASE = 1073741823; -NORETURN static void __testlib_fail(const std::string& message); +int __testlib_exitCode; + +bool __testlib_hasTestCase; +int __testlib_testCase = -1; + +void setTestCase(int testCase); + +void unsetTestCase() { + __testlib_hasTestCase = false; + __testlib_testCase = -1; +} + +NORETURN static void __testlib_fail(const std::string &message); template -static inline T __testlib_abs(const T& x) -{ +#ifdef __GNUC__ +__attribute__((const)) +#endif +static inline T __testlib_abs(const T &x) { return x > 0 ? x : -x; } template -static inline T __testlib_min(const T& a, const T& b) -{ +#ifdef __GNUC__ +__attribute__((const)) +#endif +static inline T __testlib_min(const T &a, const T &b) { return a < b ? a : b; } template -static inline T __testlib_max(const T& a, const T& b) -{ +#ifdef __GNUC__ +__attribute__((const)) +#endif +static inline T __testlib_max(const T &a, const T &b) { return a > b ? a : b; } -static bool __testlib_prelimIsNaN(double r) -{ +template +#ifdef __GNUC__ +__attribute__((const)) +#endif +static inline T __testlib_crop(T value, T a, T b) { + return __testlib_min(__testlib_max(value, a), --b); +} + +#ifdef __GNUC__ +__attribute__((const)) +#endif +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__ return ((ra != ra) == true) && ((ra == ra) == false) && ((1.0 > ra) == false) && ((1.0 < ra) == false); @@ -352,44 +415,43 @@ static bool __testlib_prelimIsNaN(double r) #endif } -static std::string removeDoubleTrailingZeroes(std::string value) -{ +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__ -__attribute__ ((format (printf, 1, 2))) -#endif -std::string format(const char* fmt, ...) -{ - FMT_TO_RESULT(fmt, fmt, result); - return result; +inline std::string upperCase(std::string s) { + for (size_t i = 0; i < s.length(); i++) + if ('a' <= s[i] && s[i] <= 'z') + s[i] = char(s[i] - 'a' + 'A'); + return s; } -std::string format(const std::string fmt, ...) -{ - FMT_TO_RESULT(fmt, fmt.c_str(), result); - return result; +inline std::string lowerCase(std::string s) { + for (size_t i = 0; i < s.length(); i++) + if ('A' <= s[i] && s[i] <= 'Z') + s[i] = char(s[i] - 'A' + 'a'); + return s; } -static std::string __testlib_part(const std::string& s); +static std::string __testlib_part(const std::string &s); -static bool __testlib_isNaN(double r) -{ +static bool __testlib_isNaN(double r) { __TESTLIB_STATIC_ASSERT(sizeof(double) == sizeof(long long)); volatile double ra = r; long long llr1, llr2; - std::memcpy((void*)&llr1, (void*)&ra, sizeof(double)); + std::memcpy((void *) &llr1, (void *) &ra, sizeof(double)); ra = -ra; - std::memcpy((void*)&llr2, (void*)&ra, sizeof(double)); + std::memcpy((void *) &llr2, (void *) &ra, sizeof(double)); long long llnan = 0xFFF8000000000000LL; return __testlib_prelimIsNaN(r) || llnan == llr1 || llnan == llr2; } -static double __testlib_nan() -{ +static double __testlib_nan() { __TESTLIB_STATIC_ASSERT(sizeof(double) == sizeof(long long)); #ifndef NAN long long llnan = 0xFFF8000000000000LL; @@ -401,8 +463,7 @@ static double __testlib_nan() #endif } -static bool __testlib_isInfinite(double r) -{ +static bool __testlib_isInfinite(double r) { volatile double ra = r; return (ra > 1E300 || ra < -1E300); } @@ -410,103 +471,235 @@ static bool __testlib_isInfinite(double r) #ifdef __GNUC__ __attribute__((const)) #endif -inline bool doubleCompare(double expected, double result, double MAX_DOUBLE_ERROR) -{ - if (__testlib_isNaN(expected)) - { - return __testlib_isNaN(result); +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)) { + if (expected > 0) { + return result > 0 && __testlib_isInfinite(result); + } else { + return result < 0 && __testlib_isInfinite(result); } - else - if (__testlib_isInfinite(expected)) - { - if (expected > 0) - { - return result > 0 && __testlib_isInfinite(result); - } - else - { - return result < 0 && __testlib_isInfinite(result); - } - } - else - if (__testlib_isNaN(result) || __testlib_isInfinite(result)) - { - return false; - } - else - if (__testlib_abs(result - expected) <= MAX_DOUBLE_ERROR + 1E-15) - { - 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; - } + } else if (__testlib_isNaN(result) || __testlib_isInfinite(result)) { + return false; + } 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 >= minv && result <= maxv; + } } #ifdef __GNUC__ __attribute__((const)) #endif -inline double doubleDelta(double expected, double result) -{ +inline double doubleDelta(double expected, double result) { double absolute = __testlib_abs(result - expected); - - if (__testlib_abs(expected) > 1E-9) - { + + if (__testlib_abs(expected) > 1E-9) { double relative = __testlib_abs(absolute / expected); return __testlib_min(absolute, relative); - } - else + } else return absolute; } -#if !defined(_MSC_VER) || _MSC_VER<1900 -#ifndef _fileno -#define _fileno(_stream) ((_stream)->_file) +/** 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 + } +} + +#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 +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; +} + +template +static std::string toHumanReadableString(const T &n) { + return vtos(n); +} #endif -#ifndef O_BINARY -static void __testlib_set_binary( -#ifdef __GNUC__ - __attribute__((unused)) +template +static std::string toString(const T &t) { + return vtos(t); +} + +#if __cplusplus > 199711L || defined(_MSC_VER) +/* opts */ +void prepareOpts(int argc, char* argv[]); #endif - std::FILE* file -) + +FILE* testlib_fopen_(const char* path, const char* mode) { +#ifdef _MSC_VER + FILE* result = NULL; + if (fopen_s(&result, path, mode) != 0) + return NULL; + else + return result; #else -static void __testlib_set_binary(std::FILE* file) + return std::fopen(path, mode); #endif -{ -#ifdef O_BINARY - if (NULL != file) - { -#ifndef __BORLANDC__ - _setmode(_fileno(file), O_BINARY); +} + +FILE* testlib_freopen_(const char* path, const char* mode, FILE* file) { +#ifdef _MSC_VER + FILE* result = NULL; + if (freopen_s(&result, path, mode, file) != 0) + return NULL; + else + return result; #else - setmode(fileno(file), O_BINARY); -#endif - } + return std::freopen(path, mode, file); #endif } /* * 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 @@ -514,31 +707,38 @@ static void __testlib_set_binary(std::FILE* file) * * 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; -class pattern -{ +class pattern { public: /* Create pattern instance by string. */ pattern(std::string s); + /* Generate new string by pattern and given random_t. */ - std::string next(random_t& rnd) const; + std::string next(random_t &rnd) const; + /* Checks if given string match the pattern. */ - bool matches(const std::string& s) const; + bool matches(const std::string &s) const; + /* Returns source string of the pattern. */ std::string src() const; + private: - bool matches(const std::string& s, size_t pos) const; + bool matches(const std::string &s, size_t pos) const; std::string s; std::vector children; @@ -547,9 +747,9 @@ class pattern int to; }; -/* - * Use random_t instances to generate random values. It is preffered - * way to use randoms instead of rand() function or self-written +/* + * Use random_t instances to generate random values. It is preferred + * way to use randoms instead of rand() function or self-written * randoms. * * Testlib defines global variable "rnd" of random_t class. @@ -559,8 +759,7 @@ class pattern * Random generates uniformly distributed values if another strategy is * not specified explicitly. */ -class random_t -{ +class random_t { private: unsigned long long seed; static const unsigned long long multiplier; @@ -568,23 +767,19 @@ class random_t static const unsigned long long mask; static const int lim; - long long nextBits(int bits) - { - if (bits <= 48) - { + long long nextBits(int bits) { + if (bits <= 48) { seed = (seed * multiplier + addend) & mask; - return (long long)(seed >> (48 - bits)); - } - else - { + return (long long) (seed >> (48 - bits)); + } else { if (bits > 63) __testlib_fail("random_t::nextBits(int bits): n must be less than 64"); int lowerBitCount = (random_t::version == 0 ? 31 : 32); - + long long left = (nextBits(31) << 32); long long right = nextBits(lowerBitCount); - + return left ^ right; } } @@ -594,41 +789,38 @@ class random_t /* New random_t with fixed seed. */ random_t() - : seed(3905348978240129619LL) - { + : seed(3905348978240129619LL) { } /* Sets seed by command line. */ - void setSeed(int argc, char* argv[]) - { + void setSeed(int argc, char *argv[]) { random_t p; seed = 3905348978240129619LL; - for (int i = 1; i < argc; i++) - { + for (int i = 1; i < argc; i++) { std::size_t le = std::strlen(argv[i]); for (std::size_t j = 0; j < le; j++) - seed = seed * multiplier + (unsigned int)(argv[i][j]) + addend; + seed = seed * multiplier + (unsigned int) (argv[i][j]) + addend; seed += multiplier / addend; } seed = seed & mask; } - /* Sets seed by given value. */ - void setSeed(long long _seed) - { - _seed = (_seed ^ multiplier) & mask; - seed = _seed; + /* Sets seed by given value. */ + void setSeed(long long _seed) { + seed = (unsigned long long) _seed; + seed = (seed ^ multiplier) & mask; } #ifndef __BORLANDC__ + /* Random string value by given pattern (see pattern documentation). */ - std::string next(const std::string& ptrn) - { + std::string next(const std::string &ptrn) { pattern p(ptrn); return p.next(*this); } + #else /* Random string value by given pattern (see pattern documentation). */ std::string next(std::string ptrn) @@ -639,16 +831,15 @@ class random_t #endif /* Random value in range [0, n-1]. */ - int next(int n) - { + int next(int n) { if (n <= 0) __testlib_fail("random_t::next(int n): n must be positive"); if ((n & -n) == n) // n is a power of 2 - return (int)((n * (long long)nextBits(31)) >> 31); + return (int) ((n * (long long) nextBits(31)) >> 31); const long long limit = INT_MAX / n * n; - + long long bits; do { bits = nextBits(31); @@ -658,21 +849,19 @@ class random_t } /* Random value in range [0, n-1]. */ - unsigned int next(unsigned int n) - { + unsigned int next(unsigned int n) { if (n >= INT_MAX) __testlib_fail("random_t::next(unsigned int n): n must be less INT_MAX"); - return (unsigned int)next(int(n)); + return (unsigned int) next(int(n)); } /* Random value in range [0, n-1]. */ - long long next(long long n) - { + long long next(long long n) { if (n <= 0) __testlib_fail("random_t::next(long long n): n must be positive"); const long long limit = __TESTLIB_LONGLONG_MAX / n * n; - + long long bits; do { bits = nextBits(63); @@ -682,120 +871,111 @@ class random_t } /* Random value in range [0, n-1]. */ - unsigned long long next(unsigned long long n) - { - if (n >= (unsigned long long)(__TESTLIB_LONGLONG_MAX)) + unsigned long long next(unsigned long long n) { + if (n >= (unsigned long long) (__TESTLIB_LONGLONG_MAX)) __testlib_fail("random_t::next(unsigned long long n): n must be less LONGLONG_MAX"); - return (unsigned long long)next((long long)(n)); + return (unsigned long long) next((long long) (n)); } /* Random value in range [0, n-1]. */ - long next(long n) - { - return (long)next((long long)(n)); + long next(long n) { + return (long) next((long long) (n)); } /* Random value in range [0, n-1]. */ - unsigned long next(unsigned long n) - { - if (n >= (unsigned long)(LONG_MAX)) + unsigned long next(unsigned long n) { + if (n >= (unsigned long) (LONG_MAX)) __testlib_fail("random_t::next(unsigned long n): n must be less LONG_MAX"); - return (unsigned long)next((unsigned long long)(n)); + return (unsigned long) next((unsigned long long) (n)); } /* Returns random value in range [from,to]. */ - int next(int from, int to) - { - return int(next((long long)to - from + 1) + from); + int next(int from, int to) { + return int(next((long long) to - from + 1) + from); } /* Returns random value in range [from,to]. */ - unsigned int next(unsigned int from, unsigned int to) - { - return (unsigned int)(next((long long)to - from + 1) + from); + unsigned int next(unsigned int from, unsigned int to) { + return (unsigned int) (next((long long) to - from + 1) + from); } /* Returns random value in range [from,to]. */ - long long next(long long from, long long to) - { + long long next(long long from, long long to) { return next(to - from + 1) + from; } /* Returns random value in range [from,to]. */ - unsigned long long next(unsigned long long from, unsigned long long to) - { + unsigned long long next(unsigned long long from, unsigned long long to) { if (from > to) __testlib_fail("random_t::next(unsigned long long from, unsigned long long to): from can't not exceed to"); return next(to - from + 1) + from; } /* Returns random value in range [from,to]. */ - long next(long from, long to) - { + long next(long from, long to) { return next(to - from + 1) + from; } /* Returns random value in range [from,to]. */ - unsigned long next(unsigned long from, unsigned long to) - { + unsigned long next(unsigned long from, unsigned long to) { if (from > to) __testlib_fail("random_t::next(unsigned long from, unsigned long to): from can't not exceed to"); return next(to - from + 1) + from; } /* Random double value in range [0, 1). */ - double next() - { - long long left = ((long long)(nextBits(26)) << 27); + 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(); + double next(double n) { + 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"); + double next(double from, double 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(); + template + typename Container::value_type any(const Container &c) { + 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)); + typename Container::const_iterator it = c.begin(); + std::advance(it, next(size)); + return *it; } /* Returns random element from iterator range. */ - template - typename Iter::value_type any(const Iter& begin, const Iter& end) - { - int size = int(end - begin); + template + typename Iter::value_type any(const Iter &begin, const Iter &end) { + int size = static_cast(std::distance(begin, end)); if (size <= 0) __testlib_fail("random_t::any(const Iter& begin, const Iter& end): range must have positive length"); - return *(begin + next(size)); + Iter it = begin; + std::advance(it, next(size)); + return *it; } /* Random string value by given pattern (see pattern documentation). */ #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) #endif - std::string next(const char* format, ...) - { + std::string next(const char *format, ...) { FMT_TO_RESULT(format, format, ptrn); return next(ptrn); } - /* + /* * Weighted next. If type == 0 than it is usual "next()". * * If type = 1, than it returns "max(next(), next())" @@ -803,253 +983,318 @@ class random_t * * If type < 0, than "max" function replaces with "min". */ - int wnext(int n, int type) - { + int wnext(int n, int type) { if (n <= 0) __testlib_fail("random_t::wnext(int n, int type): n must be positive"); - - if (abs(type) < random_t::lim) - { + + if (abs(type) < random_t::lim) { int result = next(n); for (int i = 0; i < +type; i++) result = __testlib_max(result, next(n)); - + for (int i = 0; i < -type; i++) result = __testlib_min(result, next(n)); return result; - } - else - { + } else { double p; - + if (type > 0) p = std::pow(next() + 0.0, 1.0 / (type + 1)); 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); } } - + /* See wnext(int, int). It uses the same algorithms. */ - long long wnext(long long n, int type) - { + long long wnext(long long n, int type) { if (n <= 0) __testlib_fail("random_t::wnext(long long n, int type): n must be positive"); - - if (abs(type) < random_t::lim) - { + + if (abs(type) < random_t::lim) { long long result = next(n); for (int i = 0; i < +type; i++) result = __testlib_max(result, next(n)); - + for (int i = 0; i < -type; i++) result = __testlib_min(result, next(n)); return result; - } - else - { + } 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 __testlib_min(__testlib_max((long long)(double(n) * p), 0LL), n - 1LL); + return __testlib_crop((long long) (double(n) * p), 0LL, n); } } - - /* See wnext(int, int). It uses the same algorithms. */ - double wnext(int type) - { - if (abs(type) < random_t::lim) - { + + /* 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"); + + 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 - { + return n * 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(n * p, 0.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"); - - 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 n * 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); - return n * p; - } + /* 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) - { + unsigned int wnext(unsigned int n, int type) { if (n >= INT_MAX) __testlib_fail("random_t::wnext(unsigned int n, int type): n must be less INT_MAX"); - return (unsigned int)wnext(int(n), type); + return (unsigned int) wnext(int(n), type); } - + /* See wnext(int, int). It uses the same algorithms. */ - unsigned long long wnext(unsigned long long n, int type) - { - if (n >= (unsigned long long)(__TESTLIB_LONGLONG_MAX)) + unsigned long long wnext(unsigned long long n, int type) { + if (n >= (unsigned long long) (__TESTLIB_LONGLONG_MAX)) __testlib_fail("random_t::wnext(unsigned long long n, int type): n must be less LONGLONG_MAX"); - return (unsigned long long)wnext((long long)(n), type); + return (unsigned long long) wnext((long long) (n), type); } /* See wnext(int, int). It uses the same algorithms. */ - long wnext(long n, int type) - { - return (long)wnext((long long)(n), type); + long wnext(long n, int type) { + return (long) wnext((long long) (n), type); } - + /* See wnext(int, int). It uses the same algorithms. */ - unsigned long wnext(unsigned long n, int type) - { - if (n >= (unsigned long)(LONG_MAX)) + unsigned long wnext(unsigned long n, int type) { + if (n >= (unsigned long) (LONG_MAX)) __testlib_fail("random_t::wnext(unsigned long n, int type): n must be less LONG_MAX"); - return (unsigned long)wnext((unsigned long long)(n), type); + return (unsigned long) wnext((unsigned long long) (n), type); } /* Returns weighted random value in range [from, to]. */ - int wnext(int from, int to, int type) - { + int wnext(int from, int to, int type) { if (from > to) __testlib_fail("random_t::wnext(int from, int to, int type): from can't not exceed to"); return wnext(to - from + 1, type) + from; } - + /* Returns weighted random value in range [from, to]. */ - int wnext(unsigned int from, unsigned int to, int type) - { + int wnext(unsigned int from, unsigned int to, int type) { if (from > to) __testlib_fail("random_t::wnext(unsigned int from, unsigned int to, int type): from can't not exceed to"); return int(wnext(to - from + 1, type) + from); } - + /* Returns weighted random value in range [from, to]. */ - long long wnext(long long from, long long to, int type) - { + long long wnext(long long from, long long to, int type) { if (from > to) __testlib_fail("random_t::wnext(long long from, long long to, int type): from can't not exceed to"); return wnext(to - from + 1, type) + from; } - + /* Returns weighted random value in range [from, to]. */ - unsigned long long wnext(unsigned long long from, unsigned long long to, int type) - { + unsigned long long wnext(unsigned long long from, unsigned long long to, int type) { if (from > to) - __testlib_fail("random_t::wnext(unsigned long long from, unsigned long long to, int type): from can't not exceed to"); + __testlib_fail( + "random_t::wnext(unsigned long long from, unsigned long long to, int type): from can't not exceed to"); return wnext(to - from + 1, type) + from; } - + /* Returns weighted random value in range [from, to]. */ - long wnext(long from, long to, int type) - { + long wnext(long from, long to, int type) { if (from > to) __testlib_fail("random_t::wnext(long from, long to, int type): from can't not exceed to"); return wnext(to - from + 1, type) + from; } - + /* Returns weighted random value in range [from, to]. */ - unsigned long wnext(unsigned long from, unsigned long to, int type) - { + unsigned long wnext(unsigned long from, unsigned long to, int type) { if (from > to) __testlib_fail("random_t::wnext(unsigned long from, unsigned long to, int type): from can't not exceed to"); return wnext(to - from + 1, type) + from; } - + /* 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"); + double wnext(double from, double to, int type) { + 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; } /* Returns weighted random element from container. */ - template - typename Container::value_type wany(const Container& c, int type) - { - size_t size = c.size(); + template + typename Container::value_type wany(const Container &c, int type) { + int size = int(c.size()); if (size <= 0) __testlib_fail("random_t::wany(const Container& c, int type): c.size() must be positive"); - return *(c.begin() + wnext(size, type)); + typename Container::const_iterator it = c.begin(); + std::advance(it, wnext(size, type)); + return *it; } /* Returns weighted random element from iterator range. */ - template - typename Iter::value_type wany(const Iter& begin, const Iter& end, int type) - { - int size = int(end - begin); + template + typename Iter::value_type wany(const Iter &begin, const Iter &end, int type) { + int size = static_cast(std::distance(begin, end)); if (size <= 0) - __testlib_fail("random_t::any(const Iter& begin, const Iter& end, int type): range must have positive length"); - return *(begin + wnext(size, type)); + __testlib_fail( + "random_t::any(const Iter& begin, const Iter& end, int type): range must have positive length"); + Iter it = begin; + std::advance(it, wnext(size, type)); + return *it; } + /* 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"); + std::vector perm(T size, E first) { + 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) - { + 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; @@ -1059,21 +1304,18 @@ const unsigned long long random_t::mask = (1LL << 48) - 1; int random_t::version = -1; /* Pattern implementation */ -bool pattern::matches(const std::string& s) const -{ +bool pattern::matches(const std::string &s) const { return matches(s, 0); } -static bool __pattern_isSlash(const std::string& s, size_t pos) -{ +static bool __pattern_isSlash(const std::string &s, size_t pos) { return s[pos] == '\\'; } #ifdef __GNUC__ __attribute__((pure)) #endif -static bool __pattern_isCommandChar(const std::string& s, size_t pos, char value) -{ +static bool __pattern_isCommandChar(const std::string &s, size_t pos, char value) { if (pos >= s.length()) return false; @@ -1086,8 +1328,7 @@ static bool __pattern_isCommandChar(const std::string& s, size_t pos, char value return slashes % 2 == 0 && s[pos] == value; } -static char __pattern_getChar(const std::string& s, size_t& pos) -{ +static char __pattern_getChar(const std::string &s, size_t &pos) { if (__pattern_isSlash(s, pos)) pos += 2; else @@ -1099,12 +1340,10 @@ static char __pattern_getChar(const std::string& s, size_t& pos) #ifdef __GNUC__ __attribute__((pure)) #endif -static int __pattern_greedyMatch(const std::string& s, size_t pos, const std::vector chars) -{ +static int __pattern_greedyMatch(const std::string &s, size_t pos, const std::vector chars) { int result = 0; - while (pos < s.length()) - { + while (pos < s.length()) { char c = s[pos++]; if (!std::binary_search(chars.begin(), chars.end(), c)) break; @@ -1115,17 +1354,14 @@ static int __pattern_greedyMatch(const std::string& s, size_t pos, const std::ve return result; } -std::string pattern::src() const -{ +std::string pattern::src() const { return s; } -bool pattern::matches(const std::string& s, size_t pos) const -{ +bool pattern::matches(const std::string &s, size_t pos) const { std::string result; - if (to > 0) - { + if (to > 0) { int size = __pattern_greedyMatch(s, pos, chars); if (size < from) return false; @@ -1134,34 +1370,29 @@ bool pattern::matches(const std::string& s, size_t pos) const pos += size; } - if (children.size() > 0) - { + if (children.size() > 0) { for (size_t child = 0; child < children.size(); child++) if (children[child].matches(s, pos)) return true; return false; - } - else + } else return pos == s.length(); } -std::string pattern::next(random_t& rnd) const -{ +std::string pattern::next(random_t &rnd) const { std::string result; result.reserve(20); if (to == INT_MAX) __testlib_fail("pattern::next(random_t& rnd): can't process character '*' for generation"); - if (to > 0) - { + if (to > 0) { int count = rnd.next(to - from + 1) + from; for (int i = 0; i < count; i++) result += chars[rnd.next(int(chars.size()))]; } - if (children.size() > 0) - { + if (children.size() > 0) { int child = rnd.next(int(children.size())); result += children[child].next(rnd); } @@ -1169,23 +1400,19 @@ std::string pattern::next(random_t& rnd) const return result; } -static void __pattern_scanCounts(const std::string& s, size_t& pos, int& from, int& to) -{ - if (pos >= s.length()) - { +static void __pattern_scanCounts(const std::string &s, size_t &pos, int &from, int &to) { + if (pos >= s.length()) { from = to = 1; return; } - - if (__pattern_isCommandChar(s, pos, '{')) - { + + if (__pattern_isCommandChar(s, pos, '{')) { std::vector parts; std::string part; pos++; - while (pos < s.length() && !__pattern_isCommandChar(s, pos, '}')) - { + while (pos < s.length() && !__pattern_isCommandChar(s, pos, '}')) { if (__pattern_isCommandChar(s, pos, ',')) parts.push_back(part), part = "", pos++; else @@ -1205,12 +1432,15 @@ static void __pattern_scanCounts(const std::string& s, size_t& pos, int& from, i std::vector numbers; - for (size_t i = 0; i < parts.size(); i++) - { + for (size_t i = 0; i < parts.size(); i++) { if (parts[i].length() == 0) __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); int number; +#ifdef _MSC_VER + if (sscanf_s(parts[i].c_str(), "%d", &number) != 1) +#else if (std::sscanf(parts[i].c_str(), "%d", &number) != 1) +#endif __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); numbers.push_back(number); } @@ -1222,53 +1452,45 @@ static void __pattern_scanCounts(const std::string& s, size_t& pos, int& from, i if (from > to) __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); - } - else - { - if (__pattern_isCommandChar(s, pos, '?')) - { + } else { + if (__pattern_isCommandChar(s, pos, '?')) { from = 0, to = 1, pos++; return; } - if (__pattern_isCommandChar(s, pos, '*')) - { + if (__pattern_isCommandChar(s, pos, '*')) { from = 0, to = INT_MAX, pos++; return; } - if (__pattern_isCommandChar(s, pos, '+')) - { + if (__pattern_isCommandChar(s, pos, '+')) { from = 1, to = INT_MAX, pos++; return; } - + from = to = 1; } } -static std::vector __pattern_scanCharSet(const std::string& s, size_t& pos) -{ +static std::vector __pattern_scanCharSet(const std::string &s, size_t &pos) { if (pos >= s.length()) __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); std::vector result; - if (__pattern_isCommandChar(s, pos, '[')) - { + if (__pattern_isCommandChar(s, pos, '[')) { pos++; bool negative = __pattern_isCommandChar(s, pos, '^'); + if (negative) + pos++; char prev = 0; - while (pos < s.length() && !__pattern_isCommandChar(s, pos, ']')) - { - if (__pattern_isCommandChar(s, pos, '-') && prev != 0) - { + while (pos < s.length() && !__pattern_isCommandChar(s, pos, ']')) { + if (__pattern_isCommandChar(s, pos, '-') && prev != 0) { pos++; - if (pos + 1 == s.length() || __pattern_isCommandChar(s, pos, ']')) - { + if (pos + 1 == s.length() || __pattern_isCommandChar(s, pos, ']')) { result.push_back(prev); prev = '-'; continue; @@ -1283,9 +1505,7 @@ static std::vector __pattern_scanCharSet(const std::string& s, size_t& pos result.push_back(next); prev = 0; - } - else - { + } else { if (prev != 0) result.push_back(prev); prev = __pattern_getChar(s, pos); @@ -1300,12 +1520,10 @@ static std::vector __pattern_scanCharSet(const std::string& s, size_t& pos pos++; - if (negative) - { + if (negative) { std::sort(result.begin(), result.end()); std::vector actuals; - for (int code = 0; code < 255; code++) - { + for (int code = 0; code < 255; code++) { char c = char(code); if (!std::binary_search(result.begin(), result.end(), c)) actuals.push_back(c); @@ -1314,15 +1532,13 @@ static std::vector __pattern_scanCharSet(const std::string& s, size_t& pos } std::sort(result.begin(), result.end()); - } - else + } else result.push_back(__pattern_getChar(s, pos)); return result; } -pattern::pattern(std::string s): s(s), from(0), to(0) -{ +pattern::pattern(std::string s) : s(s), from(0), to(0) { std::string t; for (size_t i = 0; i < s.length(); i++) if (!__pattern_isCommandChar(s, i, ' ')) @@ -1333,22 +1549,19 @@ pattern::pattern(std::string s): s(s), from(0), to(0) int firstClose = -1; std::vector seps; - for (size_t i = 0; i < s.length(); i++) - { - if (__pattern_isCommandChar(s, i, '(')) - { + for (size_t i = 0; i < s.length(); i++) { + if (__pattern_isCommandChar(s, i, '(')) { opened++; continue; } - if (__pattern_isCommandChar(s, i, ')')) - { + if (__pattern_isCommandChar(s, i, ')')) { opened--; if (opened == 0 && firstClose == -1) firstClose = int(i); continue; } - + if (opened < 0) __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); @@ -1359,26 +1572,19 @@ pattern::pattern(std::string s): s(s), from(0), to(0) if (opened != 0) __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); - if (seps.size() == 0 && firstClose + 1 == (int)s.length() - && __pattern_isCommandChar(s, 0, '(') && __pattern_isCommandChar(s, s.length() - 1, ')')) - { + if (seps.size() == 0 && firstClose + 1 == (int) s.length() + && __pattern_isCommandChar(s, 0, '(') && __pattern_isCommandChar(s, s.length() - 1, ')')) { children.push_back(pattern(s.substr(1, s.length() - 2))); - } - else - { - if (seps.size() > 0) - { + } else { + if (seps.size() > 0) { seps.push_back(int(s.length())); int last = 0; - for (size_t i = 0; i < seps.size(); i++) - { + for (size_t i = 0; i < seps.size(); i++) { children.push_back(pattern(s.substr(last, seps[i] - last))); last = seps[i] + 1; } - } - else - { + } else { size_t pos = 0; chars = __pattern_scanCharSet(s, pos); __pattern_scanCounts(s, pos, from, to); @@ -1387,34 +1593,49 @@ pattern::pattern(std::string s): s(s), from(0), to(0) } } } + /* End of pattern implementation */ -template -inline bool isEof(C c) -{ +template +inline bool isEof(C c) { return c == EOFC; } -template -inline bool isEoln(C c) -{ +template +inline bool isEoln(C c) { return (c == LF || c == CR); } template -inline bool isBlanks(C c) -{ +inline bool isBlanks(C c) { return (c == LF || c == CR || c == SPACE || c == TAB); } -enum TMode -{ +inline std::string trim(const std::string &s) { + if (s.empty()) + return s; + + int left = 0; + while (left < int(s.length()) && isBlanks(s[left])) + left++; + if (left >= int(s.length())) + return ""; + + int right = int(s.length()) - 1; + while (right >= 0 && isBlanks(s[right])) + right--; + if (right < 0) + return ""; + + return s.substr(left, right - left + 1); +} + +enum TMode { _input, _output, _answer }; /* Outcomes 6-15 are reserved for future use. */ -enum TResult -{ +enum TResult { _ok = 0, _wa = 1, _pe = 2, @@ -1425,157 +1646,169 @@ enum TResult _partially = 16 }; -enum TTestlibMode -{ - _unknown, _checker, _validator, _generator, _interactor +enum TTestlibMode { + _unknown, _checker, _validator, _generator, _interactor, _scorer }; #define _pc(exitCode) (TResult(_partially + (exitCode))) /* Outcomes 6-15 are reserved for future use. */ const std::string outcomes[] = { - "accepted", - "wrong-answer", - "presentation-error", - "fail", - "fail", + "accepted", + "wrong-answer", + "presentation-error", + "fail", + "fail", #ifndef PCMS2 - "points", + "points", #else - "relative-scoring", + "relative-scoring", #endif - "reserved", - "reserved", - "unexpected-eof", - "reserved", - "reserved", - "reserved", - "reserved", - "reserved", - "reserved", - "reserved", - "partially-correct" + "reserved", + "reserved", + "unexpected-eof", + "reserved", + "reserved", + "reserved", + "reserved", + "reserved", + "reserved", + "reserved", + "partially-correct" }; -class InputStreamReader -{ +class InputStreamReader { public: - virtual int curChar() = 0; - virtual int nextChar() = 0; + virtual void setTestCase(int testCase) = 0; + + virtual std::vector getReadChars() = 0; + + virtual int curChar() = 0; + + virtual int nextChar() = 0; + virtual void skipChar() = 0; + virtual void unreadChar(int c) = 0; + virtual std::string getName() = 0; + virtual bool eof() = 0; + virtual void close() = 0; + virtual int getLine() = 0; + virtual ~InputStreamReader() = 0; }; -InputStreamReader::~InputStreamReader() -{ +InputStreamReader::~InputStreamReader() { // No operations. } -class StringInputStreamReader: public InputStreamReader -{ +class StringInputStreamReader : public InputStreamReader { private: std::string s; size_t pos; public: - StringInputStreamReader(const std::string& content): s(content), pos(0) - { + StringInputStreamReader(const std::string &content) : s(content), pos(0) { // No operations. } - int curChar() - { + 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; else return s[pos]; } - int nextChar() - { - if (pos >= s.length()) - { + int nextChar() { + if (pos >= s.length()) { pos++; return EOFC; - } - else + } else return s[pos++]; } - void skipChar() - { + void skipChar() { pos++; } - void unreadChar(int c) - { + 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); } - std::string getName() - { + std::string getName() { return __testlib_part(s); } - int getLine() - { + int getLine() { return -1; } - bool eof() - { + bool eof() { return pos >= s.length(); } - void close() - { + void close() { // No operations. } }; -class FileInputStreamReader: public InputStreamReader -{ +class FileInputStreamReader : public InputStreamReader { private: - std::FILE* file; + std::FILE *file; std::string name; int line; std::vector undoChars; + std::vector readChars; + std::vector undoReadChars; - inline int postprocessGetc(int getcResult) - { + inline int postprocessGetc(int getcResult) { if (getcResult != EOF) return getcResult; else return EOFC; } - int getc(FILE* file) - { + 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*/) - { + 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); @@ -1583,57 +1816,57 @@ class FileInputStreamReader: public InputStreamReader } public: - FileInputStreamReader(std::FILE* file, const std::string& name): file(file), name(name), line(1) - { + FileInputStreamReader(std::FILE *file, const std::string &name) : file(file), name(name), line(1) { // No operations. } - int curChar() - { + void setTestCase(int testCase) { + if (testCase < 0 || testCase > __TESTLIB_MAX_TEST_CASE) + __testlib_fail(testlib_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; - else - { + else { int c = getc(file); ungetc(c/*, file*/); return postprocessGetc(c); } } - int nextChar() - { + int nextChar() { if (feof(file)) return EOFC; else return postprocessGetc(getc(file)); } - void skipChar() - { + void skipChar() { getc(file); } - void unreadChar(int c) - { + void unreadChar(int c) { ungetc(c/*, file*/); } - std::string getName() - { + std::string getName() { return name; } - int getLine() - { + int getLine() { return line; } - bool eof() - { + bool eof() { if (NULL == file || feof(file)) return true; - else - { + else { int c = nextChar(); if (c == EOFC || (c == EOF && feof(file))) return true; @@ -1642,47 +1875,42 @@ class FileInputStreamReader: public InputStreamReader } } - void close() - { - if (NULL != file) - { + void close() { + if (NULL != file) { fclose(file); file = NULL; } } }; -class BufferedFileInputStreamReader: public InputStreamReader -{ +class BufferedFileInputStreamReader : public InputStreamReader { private: static const size_t BUFFER_SIZE; - static const size_t MAX_UNREAD_COUNT; - - std::FILE* file; - char* buffer; - bool* isEof; - int bufferPos; - size_t bufferSize; + static const size_t MAX_UNREAD_COUNT; + std::FILE *file; std::string name; int line; - bool refill() - { + char *buffer; + bool *isEof; + int bufferPos; + size_t bufferSize; + + bool refill() { if (NULL == file) __testlib_fail("BufferedFileInputStreamReader: file == NULL (" + getName() + ")"); - if (bufferPos >= int(bufferSize)) - { + if (bufferPos >= int(bufferSize)) { size_t readSize = fread( - buffer + MAX_UNREAD_COUNT, - 1, - BUFFER_SIZE - MAX_UNREAD_COUNT, - file + buffer + MAX_UNREAD_COUNT, + 1, + BUFFER_SIZE - MAX_UNREAD_COUNT, + file ); if (readSize < BUFFER_SIZE - MAX_UNREAD_COUNT - && ferror(file)) + && ferror(file)) __testlib_fail("BufferedFileInputStreamReader: unable to read (" + getName() + ")"); bufferSize = MAX_UNREAD_COUNT + readSize; @@ -1690,13 +1918,11 @@ class BufferedFileInputStreamReader: public InputStreamReader std::memset(isEof + MAX_UNREAD_COUNT, 0, sizeof(isEof[0]) * readSize); return readSize > 0; - } - else + } else return true; } - char increment() - { + char increment() { char c; if ((c = buffer[bufferPos++]) == LF) line++; @@ -1704,51 +1930,51 @@ class BufferedFileInputStreamReader: public InputStreamReader } public: - BufferedFileInputStreamReader(std::FILE* file, const std::string& name): file(file), name(name), line(1) - { + BufferedFileInputStreamReader(std::FILE *file, const std::string &name) : file(file), name(name), line(1) { buffer = new char[BUFFER_SIZE]; isEof = new bool[BUFFER_SIZE]; bufferSize = MAX_UNREAD_COUNT; bufferPos = int(MAX_UNREAD_COUNT); } - ~BufferedFileInputStreamReader() - { - if (NULL != buffer) - { + ~BufferedFileInputStreamReader() { + if (NULL != buffer) { delete[] buffer; buffer = NULL; } - if (NULL != isEof) - { + if (NULL != isEof) { delete[] isEof; isEof = NULL; } } - int curChar() - { + 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; return isEof[bufferPos] ? EOFC : buffer[bufferPos]; } - int nextChar() - { + int nextChar() { if (!refill()) return EOFC; return isEof[bufferPos] ? EOFC : increment(); } - void skipChar() - { + void skipChar() { increment(); } - void unreadChar(int c) - { + void unreadChar(int c) { bufferPos--; if (bufferPos < 0) __testlib_fail("BufferedFileInputStreamReader::unreadChar(int): bufferPos < 0"); @@ -1758,25 +1984,20 @@ class BufferedFileInputStreamReader: public InputStreamReader line--; } - std::string getName() - { + std::string getName() { return name; } - int getLine() - { + int getLine() { return line; } - - bool eof() - { + + bool eof() { return !refill() || EOFC == curChar(); } - void close() - { - if (NULL != file) - { + void close() { + if (NULL != file) { fclose(file); file = NULL; } @@ -1784,23 +2005,23 @@ class BufferedFileInputStreamReader: public InputStreamReader }; const size_t BufferedFileInputStreamReader::BUFFER_SIZE = 2000000; -const size_t BufferedFileInputStreamReader::MAX_UNREAD_COUNT = BufferedFileInputStreamReader::BUFFER_SIZE / 2; +const size_t BufferedFileInputStreamReader::MAX_UNREAD_COUNT = BufferedFileInputStreamReader::BUFFER_SIZE / 2; /* * Streams to be used for reading data in checkers or validators. * Each read*() method moves pointer to the next character after the * read value. */ -struct InStream -{ +struct InStream { /* Do not use them. */ InStream(); + ~InStream(); /* Wrap std::string with InStream. */ - InStream(const InStream& baseStream, std::string content); + InStream(const InStream &baseStream, std::string content); - InputStreamReader* reader; + InputStreamReader *reader; int lastLine; std::string name; @@ -1818,349 +2039,567 @@ struct InStream size_t maxMessageLength; void init(std::string fileName, TMode mode); - void init(std::FILE* f, TMode mode); - /* Moves stream pointer to the first non-white-space character or EOF. */ + 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(); - + /* Returns current character in the stream. Doesn't remove it from stream. */ char curChar(); + /* Moves stream pointer one character forward. */ void skipChar(); + /* Returns current character and moves pointer one character forward. */ char nextChar(); - + /* Returns current character and moves pointer one character forward. */ char readChar(); + /* As "readChar()" but ensures that the result is equal to given parameter. */ char readChar(char c); + /* As "readChar()" but ensures that the result is equal to the space (code=32). */ char readSpace(); + /* Puts back the character into the stream. */ void unreadChar(char c); /* Reopens stream, you should not use it. */ - void reset(std::FILE* file = NULL); + void reset(std::FILE *file = NULL); + /* Checks that current position is EOF. If not it doesn't move stream pointer. */ bool eof(); + /* 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(); + /* Moves pointer to the first non-space and non-tab character and calls "eoln()". */ bool seekEoln(); /* 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(); - /* The same as "readWord()", it is preffered to use "readToken()". */ + + /* The same as "readWord()", it is preferred to use "readToken()". */ std::string readToken(); + /* The same as "readWord()", but ensures that token matches to given pattern. */ - std::string readWord(const std::string& ptrn, const std::string& variableName = ""); - std::string readWord(const pattern& p, const std::string& variableName = ""); - std::vector readWords(int size, const std::string& ptrn, const std::string& variablesName = "", int indexBase = 1); - std::vector readWords(int size, const pattern& p, const std::string& variablesName = "", int indexBase = 1); + std::string readWord(const std::string &ptrn, const std::string &variableName = ""); + + std::string readWord(const pattern &p, const std::string &variableName = ""); + + std::vector + readWords(int size, const std::string &ptrn, const std::string &variablesName = "", int indexBase = 1); + + std::vector + readWords(int size, const pattern &p, const std::string &variablesName = "", int indexBase = 1); + + std::vector readWords(int size, int indexBase = 1); + /* The same as "readToken()", but ensures that token matches to given pattern. */ - std::string readToken(const std::string& ptrn, const std::string& variableName = ""); - std::string readToken(const pattern& p, const std::string& variableName = ""); - std::vector readTokens(int size, const std::string& ptrn, const std::string& variablesName = "", int indexBase = 1); - std::vector readTokens(int size, const pattern& p, const std::string& variablesName = "", int indexBase = 1); - - void readWordTo(std::string& result); - void readWordTo(std::string& result, const pattern& p, const std::string& variableName = ""); - void readWordTo(std::string& result, const std::string& ptrn, const std::string& variableName = ""); - - void readTokenTo(std::string& result); - void readTokenTo(std::string& result, const pattern& p, const std::string& variableName = ""); - 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). + std::string readToken(const std::string &ptrn, const std::string &variableName = ""); + + std::string readToken(const pattern &p, const std::string &variableName = ""); + + std::vector + readTokens(int size, const std::string &ptrn, const std::string &variablesName = "", int indexBase = 1); + + std::vector + readTokens(int size, const pattern &p, const std::string &variablesName = "", int indexBase = 1); + + std::vector readTokens(int size, int indexBase = 1); + + void readWordTo(std::string &result); + + void readWordTo(std::string &result, const pattern &p, const std::string &variableName = ""); + + void readWordTo(std::string &result, const std::string &ptrn, const std::string &variableName = ""); + + void readTokenTo(std::string &result); + + void readTokenTo(std::string &result, const pattern &p, const std::string &variableName = ""); + + 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). */ 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(); /* As "readLong()" but ensures that value in the range [minv,maxv]. */ - long long readLong(long long minv, long long maxv, const std::string& variableName = ""); + long long readLong(long long minv, long long maxv, const std::string &variableName = ""); + + /* Reads space-separated sequence of long longs. */ + std::vector + readLongs(int size, long long minv, long long maxv, const std::string &variablesName = "", int indexBase = 1); + /* Reads space-separated sequence of long longs. */ - std::vector readLongs(int size, long long minv, long long maxv, const std::string& variablesName = "", int indexBase = 1); + std::vector readLongs(int size, int indexBase = 1); + + unsigned long long + readUnsignedLong(unsigned long long minv, unsigned long long maxv, const std::string &variableName = ""); + + std::vector + readUnsignedLongs(int size, unsigned long long minv, unsigned long long maxv, const std::string &variablesName = "", + int indexBase = 1); + + std::vector readUnsignedLongs(int size, int indexBase = 1); + + unsigned long long readLong(unsigned long long minv, unsigned long long maxv, const std::string &variableName = ""); - unsigned long long readUnsignedLong(unsigned long long minv, unsigned long long maxv, const std::string& variableName = ""); - std::vector readUnsignedLongs(int size, unsigned long long minv, unsigned long long maxv, const std::string& variablesName = "", int indexBase = 1); - unsigned long long readLong(unsigned long long minv, unsigned long long maxv, const std::string& variableName = ""); - std::vector readLongs(int size, unsigned long long minv, unsigned long long maxv, const std::string& variablesName = "", int indexBase = 1); + std::vector + readLongs(int size, unsigned long long minv, unsigned long long maxv, const std::string &variablesName = "", + int indexBase = 1); /* As "readInteger()" but ensures that value in the range [minv,maxv]. */ - int readInteger(int minv, int maxv, const std::string& variableName = ""); + int readInteger(int minv, int maxv, const std::string &variableName = ""); + /* As "readInt()" but ensures that value in the range [minv,maxv]. */ - int readInt(int minv, int maxv, const std::string& variableName = ""); + int readInt(int minv, int maxv, const std::string &variableName = ""); + + /* Reads space-separated sequence of integers. */ + std::vector + readIntegers(int size, int minv, int maxv, const std::string &variablesName = "", int indexBase = 1); + /* Reads space-separated sequence of integers. */ - std::vector readIntegers(int size, int minv, int maxv, const std::string& variablesName = "", int indexBase = 1); + std::vector readIntegers(int size, int indexBase = 1); + + /* Reads space-separated sequence of integers. */ + std::vector readInts(int size, int minv, int maxv, const std::string &variablesName = "", int indexBase = 1); + /* Reads space-separated sequence of integers. */ - std::vector readInts(int size, int minv, int maxv, const std::string& variablesName = "", int indexBase = 1); + 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(); - + /* As "readReal()" but ensures that value in the range [minv,maxv]. */ - double readReal(double minv, double maxv, const std::string& variableName = ""); - std::vector readReals(int size, double minv, double maxv, const std::string& variablesName = "", int indexBase = 1); + double readReal(double minv, double maxv, const std::string &variableName = ""); + + std::vector + readReals(int size, double minv, double maxv, const std::string &variablesName = "", int indexBase = 1); + + std::vector readReals(int size, int indexBase = 1); + /* As "readDouble()" but ensures that value in the range [minv,maxv]. */ - double readDouble(double minv, double maxv, const std::string& variableName = ""); - std::vector readDoubles(int size, double minv, double maxv, const std::string& variablesName = "", int indexBase = 1); - - /* + double readDouble(double minv, double maxv, const std::string &variableName = ""); + + std::vector + readDoubles(int size, double minv, double maxv, const std::string &variablesName = "", int indexBase = 1); + + 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)]". */ double readStrictReal(double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variableName = ""); + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variableName = ""); + std::vector readStrictReals(int size, double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variablesName = "", int indexBase = 1); + 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)]". */ double readStrictDouble(double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variableName = ""); + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variableName = ""); + std::vector readStrictDoubles(int size, double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variablesName = "", int indexBase = 1); - + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variablesName = "", int indexBase = 1); + /* As readLine(). */ std::string readString(); + /* Read many lines. */ std::vector readStrings(int size, int indexBase = 1); + /* See readLine(). */ - void readStringTo(std::string& result); + void readStringTo(std::string &result); + /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */ - std::string readString(const pattern& p, const std::string& variableName = ""); + std::string readString(const pattern &p, const std::string &variableName = ""); + /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */ - std::string readString(const std::string& ptrn, const std::string& variableName = ""); + std::string readString(const std::string &ptrn, const std::string &variableName = ""); + /* Read many lines. */ - std::vector readStrings(int size, const pattern& p, const std::string& variableName = "", int indexBase = 1); + std::vector + readStrings(int size, const pattern &p, const std::string &variableName = "", int indexBase = 1); + /* Read many lines. */ - std::vector readStrings(int size, const std::string& ptrn, const std::string& variableName = "", int indexBase = 1); + std::vector + readStrings(int size, const std::string &ptrn, const std::string &variableName = "", int indexBase = 1); + /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */ - void readStringTo(std::string& result, const pattern& p, const std::string& variableName = ""); + void readStringTo(std::string &result, const pattern &p, const std::string &variableName = ""); + /* 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 = ""); + 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(); + /* Read many lines. */ std::vector readLines(int size, int indexBase = 1); + /* See readLine(). */ - void readLineTo(std::string& result); + void readLineTo(std::string &result); + /* The same as "readLine()", but ensures that line matches to the given pattern. */ - std::string readLine(const pattern& p, const std::string& variableName = ""); + std::string readLine(const pattern &p, const std::string &variableName = ""); + /* The same as "readLine()", but ensures that line matches to the given pattern. */ - std::string readLine(const std::string& ptrn, const std::string& variableName = ""); + std::string readLine(const std::string &ptrn, const std::string &variableName = ""); + /* Read many lines. */ - std::vector readLines(int size, const pattern& p, const std::string& variableName = "", int indexBase = 1); + std::vector + readLines(int size, const pattern &p, const std::string &variableName = "", int indexBase = 1); + /* Read many lines. */ - std::vector readLines(int size, const std::string& ptrn, const std::string& variableName = "", int indexBase = 1); + std::vector + readLines(int size, const std::string &ptrn, const std::string &variableName = "", int indexBase = 1); + /* The same as "readLine()", but ensures that line matches to the given pattern. */ - void readLineTo(std::string& result, const pattern& p, const std::string& variableName = ""); + void readLineTo(std::string &result, const pattern &p, const std::string &variableName = ""); + /* The same as "readLine()", but ensures that line matches to the given pattern. */ - void readLineTo(std::string& result, const std::string& ptrn, const std::string& variableName = ""); + void readLineTo(std::string &result, const std::string &ptrn, const std::string &variableName = ""); /* Reads EOLN or fails. Use it in validators. Calls "eoln()" method internally. */ void readEoln(); + /* 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. */ - NORETURN void quit(TResult result, const char* msg); - /* + NORETURN void quitf(TResult result, const char *msg, ...); + + /* * Quit-functions aborts program with and : * input/answer streams replace any result to FAIL. */ - NORETURN void quitf(TResult result, const char* msg, ...); - /* + 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 codition is false. + /* + * Checks condition and aborts a program if condition is false. * Returns _wa for ouf and _fail on any other streams. */ - #ifdef __GNUC__ +#ifdef __GNUC__ __attribute__ ((format (printf, 3, 4))) - #endif - void ensuref(bool cond, const char* format, ...); +#endif + void ensuref(bool cond, const char *format, ...); + void __testlib_ensure(bool cond, std::string message); void close(); const static int NO_INDEX = INT_MAX; + const static char OPEN_BRACKET = char(11); + const static char CLOSE_BRACKET = char(17); - const static WORD LightGray = 0x07; - const static WORD LightRed = 0x0c; - const static WORD LightCyan = 0x0b; - const static WORD LightGreen = 0x0a; - const static WORD LightYellow = 0x0e; + const static WORD LightGray = 0x07; + const static WORD LightRed = 0x0c; + const static WORD LightCyan = 0x0b; + const static WORD LightGreen = 0x0a; + const static WORD LightYellow = 0x0e; static void textColor(WORD color); - static void quitscr(WORD color, const char* msg); + + static void quitscr(WORD color, const char *msg); + static void quitscrS(WORD color, std::string msg); - void xmlSafeWrite(std::FILE * file, const char* msg); + + void xmlSafeWrite(std::FILE *file, const char *msg); + + /* Skips UTF-8 Byte Order Mark. */ + void skipBom(); private: - InStream(const InStream&); - InStream& operator =(const InStream&); + InStream(const InStream &); + + InStream &operator=(const InStream &); }; InStream inf; InStream ouf; InStream ans; bool appesMode; +std::string appesModeEncoding = "windows-1251"; std::string resultName; std::string checkerName = "untitled checker"; random_t rnd; TTestlibMode testlibMode = _unknown; double __testlib_points = std::numeric_limits::infinity(); -struct ValidatorBoundsHit -{ +const size_t VALIDATOR_MAX_VARIABLE_COUNT = 255; + +struct ValidatorBoundsHit { static const double EPS; bool minHit; bool maxHit; - ValidatorBoundsHit(bool minHit = false, bool maxHit = false): minHit(minHit), maxHit(maxHit) - { + ValidatorBoundsHit(bool minHit = false, bool maxHit = false) : minHit(minHit), maxHit(maxHit) { }; - ValidatorBoundsHit merge(const ValidatorBoundsHit& validatorBoundsHit) - { + ValidatorBoundsHit merge(const ValidatorBoundsHit &validatorBoundsHit, bool ignoreMinBound, bool ignoreMaxBound) { return ValidatorBoundsHit( - __testlib_max(minHit, validatorBoundsHit.minHit), - __testlib_max(maxHit, validatorBoundsHit.maxHit) + __testlib_max(minHit, validatorBoundsHit.minHit) || ignoreMinBound, + __testlib_max(maxHit, validatorBoundsHit.maxHit) || ignoreMaxBound ); } }; +struct ConstantBound { + std::string value; + bool broken; + + template + void adjust(T t) { + std::string t_string = std::to_string(t); + if (t_string.length() >= 32) { + broken = true; + value = ""; + } else { + if (!broken && value.empty()) + value = t_string; + if (!broken && value != t_string) { + broken = true; + value = ""; + } + } + } + + bool has_value() { + return !value.empty() && !broken && value.length() < 32; + } +}; + +struct ConstantBounds { + ConstantBound lowerBound; + ConstantBound upperBound; +}; + const double ValidatorBoundsHit::EPS = 1E-12; -class Validator -{ +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::map _constantBoundsByVariableName; std::set _features; std::set _hitFeatures; + std::set _variables; - bool isVariableNameBoundsAnalyzable(const std::string& variableName) - { + bool isVariableNameBoundsAnalyzable(const std::string &variableName) { for (size_t i = 0; i < variableName.length(); i++) if ((variableName[i] >= '0' && variableName[i] <= '9') || variableName[i] < ' ') return false; return true; } - bool isFeatureNameAnalyzable(const std::string& featureName) - { + bool isFeatureNameAnalyzable(const std::string &featureName) { for (size_t i = 0; i < featureName.length(); i++) if (featureName[i] < ' ') return false; return true; } + public: - Validator(): _testset("tests"), _group() - { + Validator() : _initialized(false), _testset("tests"), _group() { } - std::string testset() const - { + 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 - { + + 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; } - std::string testOverviewLogFileName() const - { + std::string testOverviewLogFileName() const { return _testOverviewLogFileName; } - - void setTestset(const char* const testset) - { + + 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; } - void setGroup(const char* const group) - { + void setGroup(const char *const group) { _group = group; } - void setTestOverviewLogFileName(const char* const testOverviewLogFileName) - { + void setTestOverviewLogFileName(const char *const testOverviewLogFileName) { _testOverviewLogFileName = testOverviewLogFileName; } - void addBoundsHit(const std::string& variableName, ValidatorBoundsHit boundsHit) - { - if (isVariableNameBoundsAnalyzable(variableName)) - { - _boundsHitByVariableName[variableName] - = boundsHit.merge(_boundsHitByVariableName[variableName]); + void setTestMarkupFileName(const char *const testMarkupFileName) { + _testMarkupFileName = testMarkupFileName; + } + + void setTestCase(int testCase) { + _testCase = testCase; + } + + void setTestCaseFileName(const char *const testCaseFileName) { + _testCaseFileName = testCaseFileName; + } + + std::string prepVariableName(const std::string &variableName) { + if (variableName.length() >= 2 && variableName != "~~") { + if (variableName[0] == '~' && variableName.back() != '~') + return variableName.substr(1); + if (variableName[0] != '~' && variableName.back() == '~') + return variableName.substr(0, variableName.length() - 1); + if (variableName[0] == '~' && variableName.back() == '~') + return variableName.substr(1, variableName.length() - 2); } + return variableName; } - std::string getBoundsHitLog() - { + bool ignoreMinBound(const std::string &variableName) { + return variableName.length() >= 2 && variableName != "~~" && variableName[0] == '~'; + } + + bool ignoreMaxBound(const std::string &variableName) { + return variableName.length() >= 2 && variableName != "~~" && variableName.back() == '~'; + } + + void addBoundsHit(const std::string &variableName, ValidatorBoundsHit boundsHit) { + if (isVariableNameBoundsAnalyzable(variableName) + && _boundsHitByVariableName.size() < VALIDATOR_MAX_VARIABLE_COUNT) { + std::string preparedVariableName = prepVariableName(variableName); + _boundsHitByVariableName[preparedVariableName] = boundsHit.merge(_boundsHitByVariableName[preparedVariableName], + ignoreMinBound(variableName), ignoreMaxBound(variableName)); + } + } + + void addVariable(const std::string &variableName) { + if (isVariableNameBoundsAnalyzable(variableName) + && _variables.size() < VALIDATOR_MAX_VARIABLE_COUNT) { + std::string preparedVariableName = prepVariableName(variableName); + _variables.insert(preparedVariableName); + } + } + + std::string getVariablesLog() { + std::string result; + for (const std::string &variableName: _variables) + result += "variable \"" + variableName + "\"\n"; + return result; + } + + template + void adjustConstantBounds(const std::string &variableName, T lower, T upper) { + if (isVariableNameBoundsAnalyzable(variableName) + && _constantBoundsByVariableName.size() < VALIDATOR_MAX_VARIABLE_COUNT) { + std::string preparedVariableName = prepVariableName(variableName); + _constantBoundsByVariableName[preparedVariableName].lowerBound.adjust(lower); + _constantBoundsByVariableName[preparedVariableName].upperBound.adjust(upper); + } + } + + std::string getBoundsHitLog() { std::string result; for (std::map::iterator i = _boundsHitByVariableName.begin(); - i != _boundsHitByVariableName.end(); - i++) - { + i != _boundsHitByVariableName.end(); + i++) { result += "\"" + i->first + "\":"; if (i->second.minHit) result += " min-value-hit"; @@ -2171,13 +2610,32 @@ class Validator return result; } - std::string getFeaturesLog() - { + std::string getConstantBoundsLog() { + std::string result; + for (std::map::iterator i = _constantBoundsByVariableName.begin(); + i != _constantBoundsByVariableName.end(); + i++) { + if (i->second.lowerBound.has_value() || i->second.upperBound.has_value()) { + result += "constant-bounds \"" + i->first + "\":"; + if (i->second.lowerBound.has_value()) + result += " " + i->second.lowerBound.value; + else + result += " ?"; + if (i->second.upperBound.has_value()) + result += " " + i->second.upperBound.value; + else + result += " ?"; + result += "\n"; + } + } + return result; + } + + std::string getFeaturesLog() { std::string result; for (std::set::iterator i = _features.begin(); - i != _features.end(); - i++) - { + i != _features.end(); + i++) { result += "feature \"" + *i + "\":"; if (_hitFeatures.count(*i)) result += " hit"; @@ -2186,23 +2644,123 @@ class Validator return result; } - void writeTestOverviewLog() - { - if (!_testOverviewLogFileName.empty()) - { + void writeTestOverviewLog() { + if (!_testOverviewLogFileName.empty()) { std::string fileName(_testOverviewLogFileName); _testOverviewLogFileName = ""; - FILE* testOverviewLogFile = fopen(fileName.c_str(), "w"); - if (NULL == testOverviewLogFile) - __testlib_fail("Validator::writeTestOverviewLog: can't test overview log to (" + fileName + ")"); - fprintf(testOverviewLogFile, "%s%s", getBoundsHitLog().c_str(), getFeaturesLog().c_str()); - if (fclose(testOverviewLogFile)) - __testlib_fail("Validator::writeTestOverviewLog: can't close test overview log file (" + fileName + ")"); + + FILE* f; + bool standard_file = false; + if (fileName == "stdout") + f = stdout, standard_file = true; + else if (fileName == "stderr") + f = stderr, standard_file = true; + else { + f = testlib_fopen_(fileName.c_str(), "wb"); + if (NULL == f) + __testlib_fail("Validator::writeTestOverviewLog: can't write test overview log to (" + fileName + ")"); + } + fprintf(f, "%s%s%s%s", + getBoundsHitLog().c_str(), + getFeaturesLog().c_str(), + getConstantBoundsLog().c_str(), + getVariablesLog().c_str()); + std::fflush(f); + if (!standard_file) + if (std::fclose(f)) + __testlib_fail("Validator::writeTestOverviewLog: can't close test overview log file (" + fileName + ")"); } } - void addFeature(const std::string& feature) - { + 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 = testlib_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 = testlib_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."); if (!isFeatureNameAnalyzable(feature)) @@ -2211,8 +2769,7 @@ class Validator _features.insert(feature); } - void feature(const std::string& feature) - { + void feature(const std::string &feature) { if (!isFeatureNameAnalyzable(feature)) __testlib_fail("Feature name '" + feature + "' contains restricted characters."); @@ -2223,42 +2780,58 @@ class Validator } } validator; -struct TestlibFinalizeGuard -{ +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) - { + TestlibFinalizeGuard() : quitCount(0), readEofCount(0) { // No operations. } - ~TestlibFinalizeGuard() - { + ~TestlibFinalizeGuard() { bool _alive = alive; alive = false; - if (_alive) - { + if (_alive) { if (testlibMode == _checker && quitCount == 0) __testlib_fail("Checker must end with quit or quitf call."); 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. */ -void disableFinalizeGuard() -{ +void disableFinalizeGuard() { TestlibFinalizeGuard::alive = false; } @@ -2269,110 +2842,79 @@ 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; +InStream::InStream() { + reader = NULL; + lastLine = -1; + opened = false; + name = ""; + mode = _input; + strict = false; + stdfile = false; + wordReserveSize = 4; + readManyIteration = NO_INDEX; + maxFileSize = 128 * 1024 * 1024; // 128MB. + maxTokenLength = 32 * 1024 * 1024; // 32MB. + maxMessageLength = 32000; +} + +InStream::InStream(const InStream &baseStream, std::string content) { + reader = new StringInputStreamReader(content); + lastLine = -1; + opened = true; + strict = baseStream.strict; + stdfile = false; + mode = baseStream.mode; + name = "based on " + baseStream.name; + readManyIteration = NO_INDEX; + maxFileSize = 128 * 1024 * 1024; // 128MB. + maxTokenLength = 32 * 1024 * 1024; // 32MB. + maxMessageLength = 32000; +} + +InStream::~InStream() { + if (NULL != reader) { + reader->close(); + delete reader; + reader = NULL; } } -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; +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); } -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; +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(); } -#endif -template -static std::string toString(const T& t) -{ - return vtos(t); -} +void setTestCase(int testCase) { + static bool first_run = true; + static bool zero_based = false; -InStream::InStream() -{ - reader = NULL; - lastLine = -1; - name = ""; - mode = _input; - strict = false; - stdfile = false; - wordReserveSize = 4; - readManyIteration = NO_INDEX; - maxFileSize = 128 * 1024 * 1024; // 128MB. - maxTokenLength = 32 * 1024 * 1024; // 32MB. - maxMessageLength = 32000; -} + if (first_run && testCase == 0) + zero_based = true; -InStream::InStream(const InStream& baseStream, std::string content) -{ - reader = new StringInputStreamReader(content); - lastLine = -1; - opened = true; - strict = baseStream.strict; - mode = baseStream.mode; - name = "based on " + baseStream.name; - readManyIteration = NO_INDEX; - maxFileSize = 128 * 1024 * 1024; // 128MB. - maxTokenLength = 32 * 1024 * 1024; // 32MB. - maxMessageLength = 32000; -} + if (zero_based) + testCase++; -InStream::~InStream() -{ - if (NULL != reader) - { - reader->close(); - delete reader; - reader = NULL; - } + __testlib_hasTestCase = true; + __testlib_testCase = testCase; + + if (testlibMode == _validator) + inf.setTestCase(testCase); + + first_run = false; } #ifdef __GNUC__ __attribute__((const)) #endif -int resultExitCode(TResult r) -{ +int resultExitCode(TResult r) { if (r == _ok) return OK_EXIT_CODE; if (r == _wa) @@ -2397,13 +2939,12 @@ int resultExitCode(TResult r) } void InStream::textColor( -#if !(defined(ON_WINDOWS) && (!defined(_MSC_VER) || _MSC_VER>1400)) && defined(__GNUC__) - __attribute__((unused)) +#if !(defined(ON_WINDOWS) && (!defined(_MSC_VER) || _MSC_VER > 1400)) && defined(__GNUC__) + __attribute__((unused)) #endif - WORD color -) -{ -#if defined(ON_WINDOWS) && (!defined(_MSC_VER) || _MSC_VER>1400) + WORD color +) { +#if defined(ON_WINDOWS) && (!defined(_MSC_VER) || _MSC_VER > 1400) HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(handle, color); #endif @@ -2432,35 +2973,117 @@ void InStream::textColor( #endif } -NORETURN void halt(int exitCode) -{ +#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); } -static bool __testlib_shouldCheckDirt(TResult result) -{ +static bool __testlib_shouldCheckDirt(TResult result) { return result == _ok || result == _points || result >= _partially; } -NORETURN void InStream::quit(TResult result, const char* msg) -{ +static std::string __testlib_appendMessage(const std::string &message, const std::string &extra) { + int openPos = -1, closePos = -1; + for (size_t i = 0; i < message.length(); i++) { + if (message[i] == InStream::OPEN_BRACKET) { + if (openPos == -1) + openPos = int(i); + else + openPos = INT_MAX; + } + if (message[i] == InStream::CLOSE_BRACKET) { + if (closePos == -1) + closePos = int(i); + else + closePos = INT_MAX; + } + } + if (openPos != -1 && openPos != INT_MAX + && closePos != -1 && closePos != INT_MAX + && openPos < closePos) { + size_t index = message.find(extra, openPos); + if (index == std::string::npos || int(index) >= closePos) { + std::string result(message); + result.insert(closePos, ", " + extra); + return result; + } + return message; + } + + return message + " " + InStream::OPEN_BRACKET + extra + InStream::CLOSE_BRACKET; +} + +static std::string __testlib_toPrintableMessage(const std::string &message) { + int openPos = -1, closePos = -1; + for (size_t i = 0; i < message.length(); i++) { + if (message[i] == InStream::OPEN_BRACKET) { + if (openPos == -1) + openPos = int(i); + else + openPos = INT_MAX; + } + if (message[i] == InStream::CLOSE_BRACKET) { + if (closePos == -1) + closePos = int(i); + else + closePos = INT_MAX; + } + } + if (openPos != -1 && openPos != INT_MAX + && closePos != -1 && closePos != INT_MAX + && openPos < closePos) { + std::string result(message); + result[openPos] = '('; + result[closePos] = ')'; + return result; + } + + return message; +} + +NORETURN void InStream::quit(TResult result, const char *msg) { if (TestlibFinalizeGuard::alive) testlibFinalizeGuard.quitCount++; + std::string message(msg); + message = trim(message); + + if (__testlib_hasTestCase) { + if (result != _ok && result != _points) + message = __testlib_appendMessage(message, "test case " + vtos(__testlib_testCase)); + else { + if (__testlib_testCase == 1) + message = __testlib_appendMessage(message, vtos(__testlib_testCase) + " test case"); + else + message = __testlib_appendMessage(message, vtos(__testlib_testCase) + " test cases"); + } + } + // You can change maxMessageLength. // Example: 'inf.maxMessageLength = 1024 * 1024;'. - if (strlen(msg) > maxMessageLength) - { - std::string message(msg); + if (message.length() > maxMessageLength) { std::string warn = "message length exceeds " + vtos(maxMessageLength) - + ", the message is truncated: "; - msg = (warn + message.substr(0, maxMessageLength - warn.length())).c_str(); + + ", the message is truncated: "; + message = warn + message.substr(0, maxMessageLength - warn.length()); } #ifndef ENABLE_UNEXPECTED_EOF @@ -2468,19 +3091,20 @@ NORETURN void InStream::quit(TResult result, const char* msg) result = _pe; #endif - if (mode != _output && result != _fail) - { + 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, std::string(msg) + " (" + name + ", line " + vtos(lastLine) + ")"); + quits(_fail, __testlib_appendMessage(__testlib_appendMessage(message, name), "line " + vtos(lastLine))); else - quits(_fail, std::string(msg) + " (" + name + ")"); + quits(_fail, __testlib_appendMessage(message, name)); } - std::FILE * resultFile; + std::FILE *resultFile; std::string errorName; - - if (__testlib_shouldCheckDirt(result)) - { + + if (__testlib_shouldCheckDirt(result)) { if (testlibMode != _interactor && !ouf.seekEof()) quit(_dirt, "Extra information in the output file"); } @@ -2501,7 +3125,7 @@ NORETURN void InStream::quit(TResult result, const char* msg) # ifndef CMS_VERBOSE_FEEDBACK # define CMS_MSG(code, text) "translate:" code "\n" # else -# define CMS_MSG(code, text) text " %s\n", msg +# define CMS_MSG(code, text) text " %s\n", message.c_str() # endif if (result == _ok) { @@ -2517,102 +3141,102 @@ NORETURN void InStream::quit(TResult result, const char* msg) std::fprintf(stdout, "0.0\n"); std::fprintf(stderr, CMS_MSG(CMS_WRONG, "Wrong Output Format")); } else if (result == _points) { - std::string stringPoints(removeDoubleTrailingZeroes( - format("%.10f", __testlib_points))); + char pointsBuffer[64]; + std::snprintf(pointsBuffer, sizeof(pointsBuffer), "%.10f", + __testlib_points); + std::string stringPoints(removeDoubleTrailingZeroes(pointsBuffer)); std::fprintf(stdout, "%s\n", stringPoints.c_str()); std::fprintf(stderr, CMS_MSG(CMS_PARTIAL, "Partial Score")); } else if (result == _unexpected_eof) { std::fprintf(stdout, "0.0\n"); std::fprintf(stderr, CMS_MSG(CMS_WRONG, "Unexpected EOF")); } else if (result >= _partially) { - double score = (double)pctype / 200.0; + double score = (double) pctype / 200.0; std::fprintf(stdout, "%.3f\n", score); std::fprintf(stderr, CMS_MSG(CMS_PARTIAL, "Partial Score")); } else if (result == _fail) { - std::fprintf(stderr, "FAIL %s\n", msg); + std::fprintf(stderr, "FAIL %s\n", message.c_str()); halt(1); } else { - std::fprintf(stderr, "FAIL unknown result %d\n", (int)result); + std::fprintf(stderr, "FAIL unknown result %d\n", (int) result); halt(1); } halt(0); #endif - switch (result) - { - case _ok: - errorName = "ok "; - quitscrS(LightGreen, errorName); - break; - case _wa: - errorName = "wrong answer "; - quitscrS(LightRed, errorName); - break; - case _pe: - errorName = "wrong output format "; - quitscrS(LightRed, errorName); - break; - case _fail: - errorName = "FAIL "; - quitscrS(LightRed, errorName); - break; - case _dirt: - errorName = "wrong output format "; - quitscrS(LightCyan, errorName); - result = _pe; - break; - case _points: - errorName = "points "; - quitscrS(LightYellow, errorName); - break; - case _unexpected_eof: - errorName = "unexpected eof "; - quitscrS(LightCyan, errorName); - break; - default: - if (result >= _partially) - { - errorName = format("partially correct (%d) ", pctype); - isPartial = true; + switch (result) { + case _ok: + errorName = "ok "; + quitscrS(LightGreen, errorName); + break; + case _wa: + errorName = "wrong answer "; + quitscrS(LightRed, errorName); + break; + case _pe: + errorName = "wrong output format "; + quitscrS(LightRed, errorName); + break; + case _fail: + errorName = "FAIL "; + quitscrS(LightRed, errorName); + break; + case _dirt: + errorName = "wrong output format "; + quitscrS(LightCyan, errorName); + result = _pe; + break; + case _points: + errorName = "points "; quitscrS(LightYellow, errorName); - } - else - quit(_fail, "What is the code ??? "); - } - - if (resultName != "") - { - resultFile = std::fopen(resultName.c_str(), "w"); - if (resultFile == NULL) + break; + case _unexpected_eof: + errorName = "unexpected eof "; + quitscrS(LightCyan, errorName); + break; + default: + if (result >= _partially) { + errorName = testlib_format_("partially correct (%d) ", pctype); + isPartial = true; + quitscrS(LightYellow, errorName); + } else + quit(_fail, "What is the code ??? "); + } + + if (resultName != "") { + resultFile = testlib_fopen_(resultName.c_str(), "w"); + if (resultFile == NULL) { + resultName = ""; quit(_fail, "Can not write to the result file"); - if (appesMode) - { - std::fprintf(resultFile, ""); + } + if (appesMode) { + std::fprintf(resultFile, "", appesModeEncoding.c_str()); if (isPartial) - std::fprintf(resultFile, "", outcomes[(int)_partially].c_str(), pctype); - else - { + std::fprintf(resultFile, "", + outcomes[(int) _partially].c_str(), pctype); + else { if (result != _points) - std::fprintf(resultFile, "", outcomes[(int)result].c_str()); - else - { + std::fprintf(resultFile, "", outcomes[(int) result].c_str()); + else { if (__testlib_points == std::numeric_limits::infinity()) quit(_fail, "Expected points, but infinity found"); - std::string stringPoints = removeDoubleTrailingZeroes(format("%.10f", __testlib_points)); - std::fprintf(resultFile, "", outcomes[(int)result].c_str(), stringPoints.c_str()); + std::string stringPoints = removeDoubleTrailingZeroes(testlib_format_("%.10f", __testlib_points)); + std::fprintf(resultFile, "", + outcomes[(int) result].c_str(), stringPoints.c_str()); } } - xmlSafeWrite(resultFile, msg); + xmlSafeWrite(resultFile, __testlib_toPrintableMessage(message).c_str()); std::fprintf(resultFile, "\n"); - } - else - std::fprintf(resultFile, "%s", msg); - if (NULL == resultFile || fclose(resultFile) != 0) + } else + std::fprintf(resultFile, "%s", __testlib_toPrintableMessage(message).c_str()); + if (NULL == resultFile || fclose(resultFile) != 0) { + resultName = ""; quit(_fail, "Can not write to the result file"); + } } - quitscr(LightGray, msg); + quitscr(LightGray, __testlib_toPrintableMessage(message).c_str()); std::fprintf(stderr, "\n"); inf.close(); @@ -2630,46 +3254,47 @@ NORETURN void InStream::quit(TResult result, const char* msg) } #ifdef __GNUC__ - __attribute__ ((format (printf, 3, 4))) +__attribute__ ((format (printf, 3, 4))) #endif -NORETURN void InStream::quitf(TResult result, const char* msg, ...) -{ +NORETURN void InStream::quitf(TResult result, const char *msg, ...) { FMT_TO_RESULT(msg, msg, message); InStream::quit(result, message.c_str()); } -NORETURN void InStream::quits(TResult result, std::string msg) -{ +#ifdef __GNUC__ +__attribute__ ((format (printf, 4, 5))) +#endif +void InStream::quitif(bool condition, TResult result, const char *msg, ...) { + if (condition) { + FMT_TO_RESULT(msg, msg, message); + InStream::quit(result, message.c_str()); + } +} + +NORETURN void InStream::quits(TResult result, std::string msg) { InStream::quit(result, msg.c_str()); } -void InStream::xmlSafeWrite(std::FILE * file, const char* msg) -{ +void InStream::xmlSafeWrite(std::FILE *file, const char *msg) { size_t lmsg = strlen(msg); - for (size_t i = 0; i < lmsg; i++) - { - if (msg[i] == '&') - { + for (size_t i = 0; i < lmsg; i++) { + if (msg[i] == '&') { std::fprintf(file, "%s", "&"); continue; } - if (msg[i] == '<') - { + if (msg[i] == '<') { std::fprintf(file, "%s", "<"); continue; } - if (msg[i] == '>') - { + if (msg[i] == '>') { std::fprintf(file, "%s", ">"); continue; } - if (msg[i] == '"') - { + if (msg[i] == '"') { std::fprintf(file, "%s", """); continue; } - if (0 <= msg[i] && msg[i] <= 31) - { + if (0 <= msg[i] && msg[i] <= 31) { std::fprintf(file, "%c", '.'); continue; } @@ -2677,74 +3302,63 @@ void InStream::xmlSafeWrite(std::FILE * file, const char* msg) } } -void InStream::quitscrS(WORD color, std::string msg) -{ +void InStream::quitscrS(WORD color, std::string msg) { quitscr(color, msg.c_str()); } -void InStream::quitscr(WORD color, const char* msg) -{ - if (resultName == "") - { +void InStream::quitscr(WORD color, const char *msg) { + if (resultName == "") { textColor(color); std::fprintf(stderr, "%s", msg); textColor(LightGray); } } -void InStream::reset(std::FILE* file) -{ +void InStream::reset(std::FILE *file) { if (opened && stdfile) quit(_fail, "Can't reset standard handle"); if (opened) close(); - if (!stdfile) - if (NULL == (file = std::fopen(name.c_str(), "rb"))) - { + if (!stdfile && NULL == file) + if (NULL == (file = testlib_fopen_(name.c_str(), "rb"))) { if (mode == _output) quits(_pe, std::string("Output file not found: \"") + name + "\""); - + if (mode == _answer) quits(_fail, std::string("Answer file not found: \"") + name + "\""); } - if (NULL != file) - { + if (NULL != file) { opened = true; - __testlib_set_binary(file); if (stdfile) reader = new FileInputStreamReader(file, name); else reader = new BufferedFileInputStreamReader(file, name); - } - else - { + } else { opened = false; reader = NULL; } } -void InStream::init(std::string fileName, TMode mode) -{ +void InStream::init(std::string fileName, TMode mode) { opened = false; name = fileName; stdfile = false; this->mode = mode; - + std::ifstream stream; stream.open(fileName.c_str(), std::ios::in); - if (stream.is_open()) - { + if (stream.is_open()) { std::streampos start = stream.tellg(); stream.seekg(0, std::ios::end); std::streampos end = stream.tellg(); size_t fileSize = size_t(end - start); stream.close(); - + // You can change maxFileSize. // Example: 'inf.maxFileSize = 256 * 1024 * 1024;'. if (fileSize > maxFileSize) @@ -2754,12 +3368,11 @@ void InStream::init(std::string fileName, TMode mode) reset(); } -void InStream::init(std::FILE* f, TMode mode) -{ +void InStream::init(std::FILE *f, TMode mode) { opened = false; name = "untitled"; this->mode = mode; - + if (f == stdin) name = "stdin", stdfile = true; if (f == stdout) @@ -2770,64 +3383,70 @@ void InStream::init(std::FILE* f, TMode mode) reset(f); } -char InStream::curChar() -{ +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()); } -char InStream::nextChar() -{ +char InStream::nextChar() { return char(reader->nextChar()); } -char InStream::readChar() -{ +char InStream::readChar() { return nextChar(); } -char InStream::readChar(char c) -{ +char InStream::readChar(char c) { lastLine = reader->getLine(); char found = readChar(); - if (c != found) - { + if (c != found) { if (!isEoln(found)) - quit(_pe, ("Unexpected character '" + std::string(1, found) + "', but '" + std::string(1, c) + "' expected").c_str()); + quit(_pe, ("Unexpected character '" + std::string(1, found) + "', but '" + std::string(1, c) + + "' expected").c_str()); else - quit(_pe, ("Unexpected character " + ("#" + vtos(int(found))) + ", but '" + std::string(1, c) + "' expected").c_str()); + quit(_pe, ("Unexpected character " + ("#" + vtos(int(found))) + ", but '" + std::string(1, c) + + "' expected").c_str()); } return found; } -char InStream::readSpace() -{ +char InStream::readSpace() { return readChar(' '); } -void InStream::unreadChar(char c) -{ +void InStream::unreadChar(char c) { reader->unreadChar(c); } -void InStream::skipChar() -{ +void InStream::skipChar() { reader->skipChar(); } -void InStream::skipBlanks() -{ +void InStream::skipBlanks() { while (isBlanks(reader->curChar())) reader->skipChar(); } -std::string InStream::readWord() -{ +std::string InStream::readWord() { readWordTo(_tmpReadToken); return _tmpReadToken; } -void InStream::readWordTo(std::string& result) -{ +void InStream::readWordTo(std::string &result) { if (!strict) skipBlanks(); @@ -2842,14 +3461,14 @@ void InStream::readWordTo(std::string& result) result.clear(); - while (!(isBlanks(cur) || cur == EOFC)) - { + while (!(isBlanks(cur) || cur == EOFC)) { result += char(cur); - + // You can change maxTokenLength. // Example: 'inf.maxTokenLength = 128 * 1024 * 1024;'. if (result.length() > maxTokenLength) - quitf(_pe, "Length of token exceeds %d, token is '%s...'", int(maxTokenLength), __testlib_part(result).c_str()); + quitf(_pe, "Length of token exceeds %d, token is '%s...'", int(maxTokenLength), + __testlib_part(result).c_str()); cur = reader->nextChar(); } @@ -2860,22 +3479,25 @@ void InStream::readWordTo(std::string& result) quit(_unexpected_eof, "Unexpected end of file or white-space - token expected"); } -std::string InStream::readToken() -{ +std::string InStream::readToken() { return readWord(); } -void InStream::readTokenTo(std::string& result) -{ +void InStream::readTokenTo(std::string &result) { readWordTo(result); } -static std::string __testlib_part(const std::string& s) -{ - if (s.length() <= 64) - return s; +static std::string __testlib_part(const std::string &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) \ @@ -2898,98 +3520,114 @@ static std::string __testlib_part(const std::string& s) readManyIteration = NO_INDEX; \ return result; \ -std::string InStream::readWord(const pattern& p, const std::string& variableName) -{ + +std::string InStream::readWord(const pattern &p, const std::string &variableName) { readWordTo(_tmpReadToken); - if (!p.matches(_tmpReadToken)) - { - if (readManyIteration == NO_INDEX) - { + if (!p.matches(_tmpReadToken)) { + if (readManyIteration == NO_INDEX) { if (variableName.empty()) - quit(_wa, ("Token \"" + __testlib_part(_tmpReadToken) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + quit(_wa, + ("Token \"" + __testlib_part(_tmpReadToken) + "\" doesn't correspond to pattern \"" + p.src() + + "\"").c_str()); else - quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(_tmpReadToken) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); - } - else - { + quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(_tmpReadToken) + + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + } else { if (variableName.empty()) - quit(_wa, ("Token element [index=" + vtos(readManyIteration) + "] equals to \"" + __testlib_part(_tmpReadToken) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + quit(_wa, ("Token element [index=" + vtos(readManyIteration) + "] equals to \"" + + __testlib_part(_tmpReadToken) + "\" doesn't correspond to pattern \"" + p.src() + + "\"").c_str()); else - quit(_wa, ("Token element " + variableName + "[" + vtos(readManyIteration) + "] equals to \"" + __testlib_part(_tmpReadToken) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + quit(_wa, ("Token element " + variableName + "[" + vtos(readManyIteration) + "] equals to \"" + + __testlib_part(_tmpReadToken) + "\", doesn't correspond to pattern \"" + p.src() + + "\"").c_str()); } } + if (strict && !variableName.empty()) + validator.addVariable(variableName); return _tmpReadToken; } -std::vector InStream::readWords(int size, const pattern& p, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readWords(int size, const pattern &p, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readWords, readWord(p, variablesName), std::string, true); } -std::string InStream::readWord(const std::string& ptrn, const std::string& variableName) -{ +std::vector InStream::readWords(int size, int indexBase) { + __testlib_readMany(readWords, readWord(), std::string, true); +} + +std::string InStream::readWord(const std::string &ptrn, const std::string &variableName) { return readWord(pattern(ptrn), variableName); } -std::vector InStream::readWords(int size, const std::string& ptrn, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readWords(int size, const std::string &ptrn, const std::string &variablesName, int indexBase) { pattern p(ptrn); + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readWords, readWord(p, variablesName), std::string, true); } -std::string InStream::readToken(const pattern& p, const std::string& variableName) -{ +std::string InStream::readToken(const pattern &p, const std::string &variableName) { return readWord(p, variableName); } -std::vector InStream::readTokens(int size, const pattern& p, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readTokens(int size, const pattern &p, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readTokens, readToken(p, variablesName), std::string, true); } -std::string InStream::readToken(const std::string& ptrn, const std::string& variableName) -{ +std::vector InStream::readTokens(int size, int indexBase) { + __testlib_readMany(readTokens, readToken(), std::string, true); +} + +std::string InStream::readToken(const std::string &ptrn, const std::string &variableName) { return readWord(ptrn, variableName); } -std::vector InStream::readTokens(int size, const std::string& ptrn, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readTokens(int size, const std::string &ptrn, const std::string &variablesName, int indexBase) { pattern p(ptrn); + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readTokens, readWord(p, variablesName), std::string, true); } -void InStream::readWordTo(std::string& result, const pattern& p, const std::string& variableName) -{ +void InStream::readWordTo(std::string &result, const pattern &p, const std::string &variableName) { readWordTo(result); - if (!p.matches(result)) - { + if (!p.matches(result)) { if (variableName.empty()) - quit(_wa, ("Token \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + quit(_wa, ("Token \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + + "\"").c_str()); else - quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(result) + + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); } + if (strict && !variableName.empty()) + validator.addVariable(variableName); } -void InStream::readWordTo(std::string& result, const std::string& ptrn, const std::string& variableName) -{ +void InStream::readWordTo(std::string &result, const std::string &ptrn, const std::string &variableName) { return readWordTo(result, pattern(ptrn), variableName); } -void InStream::readTokenTo(std::string& result, const pattern& p, const std::string& variableName) -{ +void InStream::readTokenTo(std::string &result, const pattern &p, const std::string &variableName) { return readWordTo(result, p, variableName); } -void InStream::readTokenTo(std::string& result, const std::string& ptrn, const std::string& variableName) -{ +void InStream::readTokenTo(std::string &result, const std::string &ptrn, const std::string &variableName) { return readWordTo(result, ptrn, variableName); } #ifdef __GNUC__ __attribute__((pure)) #endif -static inline bool equals(long long integer, const char* s) -{ +static inline bool equals(long long integer, const char *s) { if (integer == LLONG_MIN) return strcmp(s, "-9223372036854775808") == 0; @@ -3010,8 +3648,7 @@ static inline bool equals(long long integer, const char* s) if (length == 0) return false; - while (integer > 0) - { + while (integer > 0) { int digit = int(integer % 10); if (s[length - 1] != '0' + digit) @@ -3027,8 +3664,7 @@ static inline bool equals(long long integer, const char* s) #ifdef __GNUC__ __attribute__((pure)) #endif -static inline bool equals(unsigned long long integer, const char* s) -{ +static inline bool equals(unsigned long long integer, const char *s) { if (integer == ULLONG_MAX) return strcmp(s, "18446744073709551615") == 0; @@ -3040,8 +3676,7 @@ static inline bool equals(unsigned long long integer, const char* s) if (length == 0) return false; - while (integer > 0) - { + while (integer > 0) { int digit = int(integer % 10); if (s[length - 1] != '0' + digit) @@ -3054,9 +3689,8 @@ static inline bool equals(unsigned long long integer, const char* s) return length == 0; } -static inline double stringToDouble(InStream& in, const char* buffer) -{ - double retval; +static inline double stringToDouble(InStream &in, const char *buffer) { + double result; size_t length = strlen(buffer); @@ -3066,12 +3700,10 @@ static inline double stringToDouble(InStream& in, const char* buffer) int digitCount = 0; int eCount = 0; - for (size_t i = 0; i < length; i++) - { + for (size_t i = 0; i < length; i++) { if (('0' <= buffer[i] && buffer[i] <= '9') || buffer[i] == '.' - || buffer[i] == 'e' || buffer[i] == 'E' - || buffer[i] == '-' || buffer[i] == '+') - { + || buffer[i] == 'e' || buffer[i] == 'E' + || buffer[i] == '-' || buffer[i] == '+') { if ('0' <= buffer[i] && buffer[i] <= '9') digitCount++; if (buffer[i] == 'e' || buffer[i] == 'E') @@ -3082,8 +3714,7 @@ static inline double stringToDouble(InStream& in, const char* buffer) plusCount++; if (buffer[i] == '.') decimalPointCount++; - } - else + } else in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); } @@ -3091,30 +3722,42 @@ static inline double stringToDouble(InStream& in, const char* buffer) if (digitCount == 0 || minusCount > 2 || plusCount > 2 || decimalPointCount > 1 || eCount > 1) in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); - char* suffix = new char[length + 1]; - int scanned = std::sscanf(buffer, "%lf%s", &retval, suffix); + char *suffix = new char[length + 1]; + std::memset(suffix, 0, length + 1); + int scanned; +#ifdef _MSC_VER + scanned = sscanf_s(buffer, "%lf%s", &result, suffix, (unsigned int)(length + 1)); +#else + scanned = std::sscanf(buffer, "%lf%s", &result, suffix); +#endif bool empty = strlen(suffix) == 0; delete[] suffix; - if (scanned == 1 || (scanned == 2 && empty)) - { - if (__testlib_isNaN(retval)) + if (scanned == 1 || (scanned == 2 && empty)) { + if (__testlib_isNaN(result)) in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); - return retval; - } - else + return result; + } else 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."); - + if (minAfterPointDigitCount > maxAfterPointDigitCount) - in.quit(_fail, "stringToStrictDouble: minAfterPointDigitCount should be less or equal to maxAfterPointDigitCount."); + in.quit(_fail, + "stringToStrictDouble: minAfterPointDigitCount should be less or equal to maxAfterPointDigitCount."); - double retval; + double result; size_t length = strlen(buffer); @@ -3124,11 +3767,9 @@ static inline double stringToStrictDouble(InStream& in, const char* buffer, int if (buffer[0] != '-' && (buffer[0] < '0' || buffer[0] > '9')) in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); - int pointPos = -1; - for (size_t i = 1; i + 1 < length; i++) - { - if (buffer[i] == '.') - { + int pointPos = -1; + for (size_t i = 1; i + 1 < length; i++) { + if (buffer[i] == '.') { if (pointPos > -1) in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); pointPos = int(i); @@ -3143,63 +3784,65 @@ static inline double stringToStrictDouble(InStream& in, const char* buffer, int int afterDigitsCount = (pointPos == -1 ? 0 : int(length) - pointPos - 1); if (afterDigitsCount < minAfterPointDigitCount || afterDigitsCount > maxAfterPointDigitCount) in.quit(_pe, ("Expected strict double with number of digits after point in range [" - + vtos(minAfterPointDigitCount) - + "," - + vtos(maxAfterPointDigitCount) - + "], but \"" + __testlib_part(buffer) + "\" found").c_str() + + vtos(minAfterPointDigitCount) + + "," + + vtos(maxAfterPointDigitCount) + + "], but \"" + __testlib_part(buffer) + "\" found").c_str() ); int firstDigitPos = -1; for (size_t i = 0; i < length; i++) - if (buffer[i] >= '0' && buffer[i] <= '9') - { + if (buffer[i] >= '0' && buffer[i] <= '9') { firstDigitPos = int(i); break; } - if (firstDigitPos > 1 || firstDigitPos == -1) + if (firstDigitPos > 1 || firstDigitPos == -1) in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); if (buffer[firstDigitPos] == '0' && firstDigitPos + 1 < int(length) - && buffer[firstDigitPos + 1] >= '0' && buffer[firstDigitPos + 1] <= '9') + && buffer[firstDigitPos + 1] >= '0' && buffer[firstDigitPos + 1] <= '9') in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); - char* suffix = new char[length + 1]; - int scanned = std::sscanf(buffer, "%lf%s", &retval, suffix); + char *suffix = new char[length + 1]; + std::memset(suffix, 0, length + 1); + int scanned; +#ifdef _MSC_VER + scanned = sscanf_s(buffer, "%lf%s", &result, suffix, (unsigned int)(length + 1)); +#else + scanned = std::sscanf(buffer, "%lf%s", &result, suffix); +#endif bool empty = strlen(suffix) == 0; delete[] suffix; - if (scanned == 1 || (scanned == 2 && empty)) - { - if (__testlib_isNaN(retval) || __testlib_isInfinite(retval)) + if (scanned == 1 || (scanned == 2 && empty)) { + if (__testlib_isNaN(result) || __testlib_isInfinite(result)) in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); - return retval; - } - else + if (buffer[0] == '-' && result >= 0) + in.quit(_pe, ("Redundant minus in \"" + __testlib_part(buffer) + "\" found").c_str()); + return result; + } else in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); } -static inline long long stringToLongLong(InStream& in, const char* buffer) -{ - if (strcmp(buffer, "-9223372036854775808") == 0) - return LLONG_MIN; +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); +} - bool minus = false; +static inline long long stringToLongLong(InStream &in, const char *buffer) { size_t length = strlen(buffer); - - if (length > 1 && buffer[0] == '-') - minus = true; - - if (length > 20) + if (length == 0 || length > 20) in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); - long long retval = 0LL; - + bool has_minus = (length > 1 && buffer[0] == '-'); int zeroes = 0; - int processingZeroes = true; - - for (int i = (minus ? 1 : 0); i < int(length); i++) - { + bool processingZeroes = true; + + for (int i = (has_minus ? 1 : 0); i < int(length); i++) { if (buffer[i] == '0' && processingZeroes) zeroes++; else @@ -3207,394 +3850,438 @@ static inline long long stringToLongLong(InStream& in, const char* buffer) if (buffer[i] < '0' || buffer[i] > '9') in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); - retval = retval * 10 + (buffer[i] - '0'); } - if (retval < 0) + long long int result; + try { + result = std::stoll(buffer); + } catch (const std::exception&) { in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); - - if ((zeroes > 0 && (retval != 0 || minus)) || zeroes > 1) + } catch (...) { in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + } - retval = (minus ? -retval : +retval); + if ((zeroes > 0 && (result != 0 || has_minus)) || zeroes > 1) + in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); - if (length < 19) - return retval; + return result; +} - if (equals(retval, buffer)) - return retval; - else - 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) -{ +static inline unsigned long long stringToUnsignedLongLong(InStream &in, const char *buffer) { size_t length = strlen(buffer); - if (length > 20) + if (length == 0 || length > 20) in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); if (length > 1 && buffer[0] == '0') in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); - unsigned long long retval = 0LL; - for (int i = 0; i < int(length); i++) - { + for (int i = 0; i < int(length); i++) { if (buffer[i] < '0' || buffer[i] > '9') in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); - retval = retval * 10 + (buffer[i] - '0'); } - if (length < 19) - return retval; + unsigned long long result; + try { + result = std::stoull(buffer); + } catch (const std::exception&) { + in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + } catch (...) { + in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + } - if (length == 20 && strcmp(buffer, "18446744073709551615") == 1) - in.quit(_pe, ("Expected unsigned int64, but \"" + __testlib_part(buffer) + "\" found").c_str()); + return result; +} - if (equals(retval, buffer)) - return retval; - else - 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() -{ +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()); - + return int(value); } -long long InStream::readLong() -{ +long long InStream::readLong() { if (!strict && seekEof()) quit(_unexpected_eof, "Unexpected end of file - int64 expected"); readWordTo(_tmpReadToken); - return stringToLongLong(*this, _tmpReadToken.c_str()); + return stringToLongLong(*this, _tmpReadToken); } -unsigned long long InStream::readUnsignedLong() -{ +unsigned long long InStream::readUnsignedLong() { if (!strict && seekEof()) quit(_unexpected_eof, "Unexpected end of file - int64 expected"); 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) -{ +long long InStream::readLong(long long minv, long long maxv, const std::string &variableName) { long long result = readLong(); - if (result < minv || result > maxv) - { - if (readManyIteration == NO_INDEX) - { + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { if (variableName.empty()) - quit(_wa, ("Integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + 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()); - } - else - { + quit(_wa, ("Integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + + ", 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()); + quit(_wa, ("Integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + + ", 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()); + quit(_wa, + ("Integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + + vtos(result) + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); } } - if (strict && !variableName.empty()) + if (strict && !variableName.empty()) { validator.addBoundsHit(variableName, ValidatorBoundsHit(minv == result, maxv == result)); + validator.adjustConstantBounds(variableName, minv, maxv); + validator.addVariable(variableName); + } return result; } -std::vector InStream::readLongs(int size, long long minv, long long maxv, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readLongs(int size, long long minv, long long maxv, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readLongs, readLong(minv, maxv, variablesName), long long, true) } -unsigned long long InStream::readUnsignedLong(unsigned long long minv, unsigned long long maxv, const std::string& variableName) -{ +std::vector InStream::readLongs(int size, int indexBase) { + __testlib_readMany(readLongs, readLong(), long long, true) +} + +unsigned long long +InStream::readUnsignedLong(unsigned long long minv, unsigned long long maxv, const std::string &variableName) { unsigned long long result = readUnsignedLong(); - if (result < minv || result > maxv) - { - if (readManyIteration == NO_INDEX) - { + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { if (variableName.empty()) - quit(_wa, ("Unsigned integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + quit(_wa, + ("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()); - } - else - { + quit(_wa, + ("Unsigned integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + + ", 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()); + quit(_wa, + ("Unsigned integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + + ", 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) + "]").c_str()); + quit(_wa, ("Unsigned integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + + "] equals to " + vtos(result) + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + + "]").c_str()); } } - if (strict && !variableName.empty()) + if (strict && !variableName.empty()) { validator.addBoundsHit(variableName, ValidatorBoundsHit(minv == result, maxv == result)); + validator.adjustConstantBounds(variableName, minv, maxv); + validator.addVariable(variableName); + } return result; } -std::vector InStream::readUnsignedLongs(int size, unsigned long long minv, unsigned long long maxv, const std::string& variablesName, int indexBase) -{ +std::vector InStream::readUnsignedLongs(int size, unsigned long long minv, unsigned long long maxv, + const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readUnsignedLongs, readUnsignedLong(minv, maxv, variablesName), unsigned long long, true) } -unsigned long long InStream::readLong(unsigned long long minv, unsigned long long maxv, const std::string& variableName) -{ +std::vector InStream::readUnsignedLongs(int size, int indexBase) { + __testlib_readMany(readUnsignedLongs, readUnsignedLong(), unsigned long long, true) +} + +unsigned long long +InStream::readLong(unsigned long long minv, unsigned long long maxv, const std::string &variableName) { return readUnsignedLong(minv, maxv, variableName); } -int InStream::readInt() -{ +int InStream::readInt() { return readInteger(); } -int InStream::readInt(int minv, int maxv, const std::string& variableName) -{ +int InStream::readInt(int minv, int maxv, const std::string &variableName) { int result = readInt(); - if (result < minv || result > maxv) - { - if (readManyIteration == NO_INDEX) - { + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { if (variableName.empty()) - quit(_wa, ("Integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + 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()); - } - else - { + quit(_wa, ("Integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + + ", 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()); + quit(_wa, ("Integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + + ", 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()); + quit(_wa, + ("Integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + + vtos(result) + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); } } - if (strict && !variableName.empty()) + if (strict && !variableName.empty()) { validator.addBoundsHit(variableName, ValidatorBoundsHit(minv == result, maxv == result)); + validator.adjustConstantBounds(variableName, minv, maxv); + validator.addVariable(variableName); + } return result; } -int InStream::readInteger(int minv, int maxv, const std::string& variableName) -{ +int InStream::readInteger(int minv, int maxv, const std::string &variableName) { return readInt(minv, maxv, variableName); } -std::vector InStream::readInts(int size, int minv, int maxv, const std::string& variablesName, int indexBase) -{ +std::vector InStream::readInts(int size, int minv, int maxv, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readInts, readInt(minv, maxv, variablesName), int, true) } -std::vector InStream::readIntegers(int size, int minv, int maxv, const std::string& variablesName, int indexBase) -{ +std::vector InStream::readInts(int size, int indexBase) { + __testlib_readMany(readInts, readInt(), int, true) +} + +std::vector InStream::readIntegers(int size, int minv, int maxv, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readIntegers, readInt(minv, maxv, variablesName), int, true) } -double InStream::readReal() -{ +std::vector InStream::readIntegers(int size, int indexBase) { + __testlib_readMany(readIntegers, readInt(), int, true) +} + +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() -{ +double InStream::readDouble() { return readReal(); } -double InStream::readReal(double minv, double maxv, const std::string& variableName) -{ +double InStream::readReal(double minv, double maxv, const std::string &variableName) { double result = readReal(); - if (result < minv || result > maxv) - { - if (readManyIteration == NO_INDEX) - { + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { if (variableName.empty()) - quit(_wa, ("Double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + quit(_wa, ("Double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + + "]").c_str()); else - quit(_wa, ("Double parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); - } - else - { + quit(_wa, ("Double parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } else { if (variableName.empty()) - quit(_wa, ("Double element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + quit(_wa, ("Double element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); else - quit(_wa, ("Double element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + quit(_wa, + ("Double element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); } } - if (strict && !variableName.empty()) + if (strict && !variableName.empty()) { validator.addBoundsHit(variableName, ValidatorBoundsHit( - doubleDelta(minv, result) < ValidatorBoundsHit::EPS, - doubleDelta(maxv, result) < ValidatorBoundsHit::EPS + doubleDelta(minv, result) < ValidatorBoundsHit::EPS, + doubleDelta(maxv, result) < ValidatorBoundsHit::EPS )); - + validator.adjustConstantBounds(variableName, minv, maxv); + validator.addVariable(variableName); + } + return result; } -std::vector InStream::readReals(int size, double minv, double maxv, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readReals(int size, double minv, double maxv, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readReals, readReal(minv, maxv, variablesName), double, true) } -double InStream::readDouble(double minv, double maxv, const std::string& variableName) -{ +std::vector InStream::readReals(int size, int indexBase) { + __testlib_readMany(readReals, readReal(), double, true) +} + +double InStream::readDouble(double minv, double maxv, const std::string &variableName) { return readReal(minv, maxv, variableName); -} +} -std::vector InStream::readDoubles(int size, double minv, double maxv, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readDoubles(int size, double minv, double maxv, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readDoubles, readDouble(minv, maxv, variablesName), double, true) } +std::vector InStream::readDoubles(int size, int indexBase) { + __testlib_readMany(readDoubles, readDouble(), double, true) +} + double InStream::readStrictReal(double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variableName) -{ + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variableName) { 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) - { + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { if (variableName.empty()) - quit(_wa, ("Strict double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + quit(_wa, ("Strict double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + + "]").c_str()); else - quit(_wa, ("Strict double parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); - } - else - { + quit(_wa, + ("Strict double parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } else { if (variableName.empty()) - quit(_wa, ("Strict double element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + quit(_wa, ("Strict double element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); else - quit(_wa, ("Strict double element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + quit(_wa, ("Strict double element " + std::string(variableName) + "[" + vtos(readManyIteration) + + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + + "]").c_str()); } } - if (strict && !variableName.empty()) + if (strict && !variableName.empty()) { validator.addBoundsHit(variableName, ValidatorBoundsHit( - doubleDelta(minv, result) < ValidatorBoundsHit::EPS, - doubleDelta(maxv, result) < ValidatorBoundsHit::EPS + doubleDelta(minv, result) < ValidatorBoundsHit::EPS, + doubleDelta(maxv, result) < ValidatorBoundsHit::EPS )); + validator.adjustConstantBounds(variableName, minv, maxv); + validator.addVariable(variableName); + } return result; } std::vector InStream::readStrictReals(int size, double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variablesName, int indexBase) -{ - __testlib_readMany(readStrictReals, readStrictReal(minv, maxv, minAfterPointDigitCount, maxAfterPointDigitCount, variablesName), double, true) + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readStrictReals, + readStrictReal(minv, maxv, minAfterPointDigitCount, maxAfterPointDigitCount, variablesName), + double, true) } double InStream::readStrictDouble(double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variableName) -{ + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variableName) { return readStrictReal(minv, maxv, - minAfterPointDigitCount, maxAfterPointDigitCount, - variableName); + minAfterPointDigitCount, maxAfterPointDigitCount, + variableName); } std::vector InStream::readStrictDoubles(int size, double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variablesName, int indexBase) -{ - __testlib_readMany(readStrictDoubles, readStrictDouble(minv, maxv, minAfterPointDigitCount, maxAfterPointDigitCount, variablesName), double, true) + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readStrictDoubles, + readStrictDouble(minv, maxv, minAfterPointDigitCount, maxAfterPointDigitCount, variablesName), + double, true) } -bool InStream::eof() -{ +bool InStream::eof() { if (!strict && NULL == reader) return true; return reader->eof(); } -bool InStream::seekEof() -{ +bool InStream::seekEof() { if (!strict && NULL == reader) return true; skipBlanks(); return eof(); } -bool InStream::eoln() -{ +bool InStream::eoln() { if (!strict && NULL == reader) return true; int c = reader->nextChar(); - if (!strict) - { + if (!strict) { if (c == EOFC) return true; - if (c == CR) - { + if (c == CR) { c = reader->nextChar(); - if (c != LF) - { + if (c != LF) { reader->unreadChar(c); reader->unreadChar(CR); return false; - } - else + } else return true; } - + if (c == LF) return true; reader->unreadChar(c); return false; - } - else - { + } else { bool returnCr = false; #if (defined(ON_WINDOWS) && !defined(FOR_LINUX)) || defined(FOR_WINDOWS) - if (c != CR) - { + if (c != CR) { reader->unreadChar(c); return false; - } - else - { + } else { if (!returnCr) returnCr = true; c = reader->nextChar(); } -#endif - if (c != LF) - { +#endif + if (c != LF) { reader->unreadChar(c); if (returnCr) reader->unreadChar(CR); @@ -3605,15 +4292,13 @@ bool InStream::eoln() } } -void InStream::readEoln() -{ +void InStream::readEoln() { lastLine = reader->getLine(); if (!eoln()) quit(_pe, "Expected EOLN"); } -void InStream::readEof() -{ +void InStream::readEof() { lastLine = reader->getLine(); if (!eof()) quit(_pe, "Expected EOF"); @@ -3622,46 +4307,38 @@ void InStream::readEof() testlibFinalizeGuard.readEofCount++; } -bool InStream::seekEoln() -{ +bool InStream::seekEoln() { if (!strict && NULL == reader) return true; - + int cur; - do - { + do { cur = reader->nextChar(); - } - while (cur == SPACE || cur == TAB); + } while (cur == SPACE || cur == TAB); reader->unreadChar(cur); return eoln(); } -void InStream::nextLine() -{ +void InStream::nextLine() { readLine(); } -void InStream::readStringTo(std::string& result) -{ +void InStream::readStringTo(std::string &result) { if (NULL == reader) quit(_pe, "Expected line"); result.clear(); - for (;;) - { + for (;;) { int cur = reader->curChar(); if (cur == LF || cur == EOFC) break; - if (cur == CR) - { + if (cur == CR) { cur = reader->nextChar(); - if (reader->curChar() == LF) - { + if (reader->curChar() == LF) { reader->unreadChar(cur); break; } @@ -3677,157 +4354,165 @@ void InStream::readStringTo(std::string& result) eoln(); } -std::string InStream::readString() -{ +std::string InStream::readString() { readStringTo(_tmpReadToken); return _tmpReadToken; } -std::vector InStream::readStrings(int size, int indexBase) -{ +std::vector InStream::readStrings(int size, int indexBase) { __testlib_readMany(readStrings, readString(), std::string, false) } -void InStream::readStringTo(std::string& result, const pattern& p, const std::string& variableName) -{ +void InStream::readStringTo(std::string &result, const pattern &p, const std::string &variableName) { readStringTo(result); - if (!p.matches(result)) - { - if (readManyIteration == NO_INDEX) - { + if (!p.matches(result)) { + if (readManyIteration == NO_INDEX) { if (variableName.empty()) - quit(_wa, ("Line \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + quit(_wa, ("Line \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + + "\"").c_str()); else - quit(_wa, ("Line [name=" + variableName + "] equals to \"" + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); - } - else - { + quit(_wa, ("Line [name=" + variableName + "] equals to \"" + __testlib_part(result) + + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + } else { if (variableName.empty()) - quit(_wa, ("Line element [index=" + vtos(readManyIteration) + "] equals to \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + quit(_wa, + ("Line element [index=" + vtos(readManyIteration) + "] equals to \"" + __testlib_part(result) + + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); else - quit(_wa, ("Line element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to \"" + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + quit(_wa, + ("Line element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to \"" + + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); } } + if (strict && !variableName.empty()) + validator.addVariable(variableName); } -void InStream::readStringTo(std::string& result, const std::string& ptrn, const std::string& variableName) -{ +void InStream::readStringTo(std::string &result, const std::string &ptrn, const std::string &variableName) { readStringTo(result, pattern(ptrn), variableName); } -std::string InStream::readString(const pattern& p, const std::string& variableName) -{ +std::string InStream::readString(const pattern &p, const std::string &variableName) { readStringTo(_tmpReadToken, p, variableName); return _tmpReadToken; } -std::vector InStream::readStrings(int size, const pattern& p, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readStrings(int size, const pattern &p, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readStrings, readString(p, variablesName), std::string, false) } -std::string InStream::readString(const std::string& ptrn, const std::string& variableName) -{ +std::string InStream::readString(const std::string &ptrn, const std::string &variableName) { readStringTo(_tmpReadToken, ptrn, variableName); return _tmpReadToken; } -std::vector InStream::readStrings(int size, const std::string& ptrn, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readStrings(int size, const std::string &ptrn, const std::string &variablesName, int indexBase) { pattern p(ptrn); + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readStrings, readString(p, variablesName), std::string, false) } -void InStream::readLineTo(std::string& result) -{ +void InStream::readLineTo(std::string &result) { readStringTo(result); } -std::string InStream::readLine() -{ +std::string InStream::readLine() { return readString(); } -std::vector InStream::readLines(int size, int indexBase) -{ +std::vector InStream::readLines(int size, int indexBase) { __testlib_readMany(readLines, readString(), std::string, false) } -void InStream::readLineTo(std::string& result, const pattern& p, const std::string& variableName) -{ +void InStream::readLineTo(std::string &result, const pattern &p, const std::string &variableName) { readStringTo(result, p, variableName); } -void InStream::readLineTo(std::string& result, const std::string& ptrn, const std::string& variableName) -{ +void InStream::readLineTo(std::string &result, const std::string &ptrn, const std::string &variableName) { readStringTo(result, ptrn, variableName); } -std::string InStream::readLine(const pattern& p, const std::string& variableName) -{ +std::string InStream::readLine(const pattern &p, const std::string &variableName) { return readString(p, variableName); } -std::vector InStream::readLines(int size, const pattern& p, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readLines(int size, const pattern &p, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readLines, readString(p, variablesName), std::string, false) } -std::string InStream::readLine(const std::string& ptrn, const std::string& variableName) -{ +std::string InStream::readLine(const std::string &ptrn, const std::string &variableName) { return readString(ptrn, variableName); } -std::vector InStream::readLines(int size, const std::string& ptrn, const std::string& variablesName, int indexBase) -{ +std::vector +InStream::readLines(int size, const std::string &ptrn, const std::string &variablesName, int indexBase) { pattern p(ptrn); + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); __testlib_readMany(readLines, readString(p, variablesName), std::string, false) } #ifdef __GNUC__ __attribute__ ((format (printf, 3, 4))) #endif -void InStream::ensuref(bool cond, const char* format, ...) -{ - if (!cond) - { +void InStream::ensuref(bool cond, const char *format, ...) { + if (!cond) { FMT_TO_RESULT(format, format, message); this->__testlib_ensure(cond, message); } } -void InStream::__testlib_ensure(bool cond, std::string message) -{ +void InStream::__testlib_ensure(bool cond, std::string message) { if (!cond) - this->quit(_wa, message.c_str()); + this->quit(_wa, message.c_str()); } -void InStream::close() -{ - if (NULL != reader) - { +void InStream::close() { + if (NULL != reader) { reader->close(); delete reader; reader = NULL; } - + opened = false; } -NORETURN void quit(TResult result, const std::string& msg) -{ +NORETURN void quit(TResult result, const std::string &msg) { ouf.quit(result, msg.c_str()); } -NORETURN void quit(TResult result, const char* msg) -{ +NORETURN void quit(TResult result, const char *msg) { ouf.quit(result, msg); } -NORETURN void __testlib_quitp(double points, const char* message) -{ - __testlib_points = points; - std::string stringPoints = removeDoubleTrailingZeroes(format("%.10f", points)); +double __testlib_preparePoints(double points_) { + volatile double points = points_; + if (__testlib_isNaN(points)) + quit(_fail, "Parameter 'points' can't be nan"); + if (__testlib_isInfinite(points)) + quit(_fail, "Parameter 'points' can't be infinite"); + if (points < -1E-8) + quit(_fail, "Parameter 'points' can't be negative"); + if (points <= 0.0) + points = +0.0; + if (points > 1E6 + 1E-8) + quit(_fail, "Parameter 'points' can't be greater than 1E6"); + if (points >= 1E6) + points = 1E6; + return points; +} + +NORETURN void __testlib_quitp(double points, const char *message) { + __testlib_points = __testlib_preparePoints(points); + std::string stringPoints = removeDoubleTrailingZeroes(testlib_format_("%.10f", __testlib_points)); std::string quitMessage; if (NULL == message || 0 == strlen(message)) @@ -3838,10 +4523,9 @@ NORETURN void __testlib_quitp(double points, const char* message) quit(_points, quitMessage.c_str()); } -NORETURN void __testlib_quitp(int points, const char* message) -{ - __testlib_points = points; - std::string stringPoints = format("%d", points); +NORETURN void __testlib_quitp(int points, const char *message) { + __testlib_points = __testlib_preparePoints(points); + std::string stringPoints = testlib_format_("%d", points); std::string quitMessage; if (NULL == message || 0 == strlen(message)) @@ -3852,32 +4536,36 @@ NORETURN void __testlib_quitp(int points, const char* message) quit(_points, quitMessage.c_str()); } -NORETURN void quitp(float points, const std::string& message = "") -{ +NORETURN void quitp(float points, const std::string &message = "") { __testlib_quitp(double(points), message.c_str()); } -NORETURN void quitp(double points, const std::string& message = "") -{ +NORETURN void quitp(double points, const std::string &message = "") { __testlib_quitp(points, message.c_str()); } -NORETURN void quitp(long double points, const std::string& message = "") -{ +NORETURN void quitp(long double points, const std::string &message = "") { __testlib_quitp(double(points), message.c_str()); } -NORETURN void quitp(int points, const std::string& message = "") -{ +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))) #endif -NORETURN void quitp(F points, const char* format, ...) -{ +NORETURN void quitp(F points, const char *format, ...) { FMT_TO_RESULT(format, format, message); quitp(points, message); } @@ -3885,8 +4573,7 @@ NORETURN void quitp(F points, const char* format, ...) #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) #endif -NORETURN void quitf(TResult result, const char* format, ...) -{ +NORETURN void quitf(TResult result, const char *format, ...) { FMT_TO_RESULT(format, format, message); quit(result, message); } @@ -3894,39 +4581,35 @@ NORETURN void quitf(TResult result, const char* format, ...) #ifdef __GNUC__ __attribute__ ((format (printf, 3, 4))) #endif -void quitif(bool condition, TResult result, const char* format, ...) -{ - if (condition) - { +void quitif(bool condition, TResult result, const char *format, ...) { + if (condition) { FMT_TO_RESULT(format, format, message); quit(result, message); } } -NORETURN void __testlib_help() -{ +NORETURN void __testlib_help() { InStream::textColor(InStream::LightCyan); std::fprintf(stderr, "TESTLIB %s, https://github.com/MikeMirzayanov/testlib/ ", VERSION); - std::fprintf(stderr, "by Mike Mirzayanov, copyright(c) 2005-2018\n"); + std::fprintf(stderr, "by Mike Mirzayanov, copyright(c) 2005-2020\n"); std::fprintf(stderr, "Checker name: \"%s\"\n", checkerName.c_str()); InStream::textColor(InStream::LightGray); std::fprintf(stderr, "\n"); std::fprintf(stderr, "Latest features: \n"); - for (size_t i = 0; i < sizeof(latestFeatures) / sizeof(char*); i++) - { + for (size_t i = 0; i < sizeof(latestFeatures) / sizeof(char *); i++) { std::fprintf(stderr, "*) %s\n", latestFeatures[i]); } 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); } -static void __testlib_ensuresPreconditions() -{ +static void __testlib_ensuresPreconditions() { // testlib assumes: sizeof(int) = 4. __TESTLIB_STATIC_ASSERT(sizeof(int) == 4); @@ -3946,17 +4629,49 @@ static void __testlib_ensuresPreconditions() quit(_fail, "Function __testlib_isNaN is not working correctly: possible reason is '-ffast-math'"); } -void registerGen(int argc, char* argv[], int randomGeneratorVersion) -{ +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); rnd.setSeed(argc, argv); + +#if __cplusplus > 199711L || defined(_MSC_VER) + prepareOpts(argc, argv); +#endif } #ifdef USE_RND_AS_BEFORE_087 @@ -3967,73 +4682,92 @@ void registerGen(int argc, char* argv[]) #else #ifdef __GNUC__ #if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 4)) - __attribute__ ((deprecated("Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." - " The third parameter stands for the random generator version." - " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." - " Version 1 has been released on Spring, 2013. Use it to write new generators."))) +__attribute__ ((deprecated("Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." +" The third parameter stands for the random generator version." +" If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." +" Version 1 has been released on Spring, 2013. Use it to write new generators."))) #else - __attribute__ ((deprecated)) +__attribute__ ((deprecated)) #endif #endif #ifdef _MSC_VER - __declspec(deprecated("Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." - " The third parameter stands for the random generator version." - " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." - " Version 1 has been released on Spring, 2013. Use it to write new generators.")) +__declspec(deprecated("Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." + " The third parameter stands for the random generator version." + " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." + " Version 1 has been released on Spring, 2013. Use it to write new generators.")) #endif -void registerGen(int argc, char* argv[]) -{ +void registerGen(int argc, char *argv[]) { std::fprintf(stderr, "Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." - " The third parameter stands for the random generator version." - " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." - " Version 1 has been released on Spring, 2013. Use it to write new generators.\n\n"); + " The third parameter stands for the random generator version." + " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." + " Version 1 has been released on Spring, 2013. Use it to write new generators.\n\n"); registerGen(argc, argv, 0); } #endif -void registerInteraction(int argc, char* argv[]) -{ +void setAppesModeEncoding(std::string appesModeEncoding) { + static const char* const ENCODINGS[] = {"ascii", "utf-7", "utf-8", "utf-16", "utf-16le", "utf-16be", "utf-32", "utf-32le", "utf-32be", "iso-8859-1", +"iso-8859-2", "iso-8859-3", "iso-8859-4", "iso-8859-5", "iso-8859-6", "iso-8859-7", "iso-8859-8", "iso-8859-9", "iso-8859-10", "iso-8859-11", +"iso-8859-13", "iso-8859-14", "iso-8859-15", "iso-8859-16", "windows-1250", "windows-1251", "windows-1252", "windows-1253", "windows-1254", "windows-1255", +"windows-1256", "windows-1257", "windows-1258", "gb2312", "gbk", "gb18030", "big5", "shift-jis", "euc-jp", "euc-kr", +"euc-cn", "euc-tw", "koi8-r", "koi8-u", "tis-620", "ibm437", "ibm850", "ibm852", "ibm855", "ibm857", +"ibm860", "ibm861", "ibm862", "ibm863", "ibm865", "ibm866", "ibm869", "macroman", "maccentraleurope", "maciceland", +"maccroatian", "macromania", "maccyrillic", "macukraine", "macgreek", "macturkish", "machebrew", "macarabic", "macthai", "hz-gb-2312", +"iso-2022-jp", "iso-2022-kr", "iso-2022-cn", "armscii-8", "tscii", "iscii", "viscii", "geostd8", "cp949", "cp874", +"cp1006", "cp775", "cp858", "cp737", "cp853", "cp856", "cp922", "cp1046", "cp1125", "cp1131", +"ptcp154", "koi8-t", "koi8-ru", "mulelao-1", "cp1133", "iso-ir-166", "tcvn", "iso-ir-14", "iso-ir-87", "iso-ir-159"}; + + appesModeEncoding = lowerCase(appesModeEncoding); + bool valid = false; + for (size_t i = 0; i < sizeof(ENCODINGS) / sizeof(ENCODINGS[0]); i++) + if (appesModeEncoding == ENCODINGS[i]) { + valid = true; + break; + } + if (!valid) + quit(_fail, "Unexpected encoding for setAppesModeEncoding(encoding)"); + ::appesModeEncoding = appesModeEncoding; +} + +void registerInteraction(int argc, char *argv[]) { #ifdef CMS - quit(_fail, "Interactors are not supported"); + TestlibFinalizeGuard::registered = true; + std::fprintf(stderr, "FAIL Interactors are not supported\n"); + halt(1); #endif __testlib_ensuresPreconditions(); + __testlib_set_testset_and_group(argc, argv); + TestlibFinalizeGuard::registered = true; testlibMode = _interactor; __testlib_set_binary(stdin); if (argc > 1 && !strcmp("--help", argv[1])) __testlib_help(); - - if (argc < 3 || argc > 6) - { + + if (argc < 3 || argc > 6) { quit(_fail, std::string("Program must be run with the following arguments: ") + - std::string(" [ [ [<-appes>]]]") + - "\nUse \"--help\" to get help information"); + std::string(" [ [ [<-appes>]]]") + + "\nUse \"--help\" to get help information"); } - if (argc <= 4) - { + if (argc <= 4) { resultName = ""; appesMode = false; } #ifndef EJUDGE - if (argc == 5) - { + if (argc == 5) { resultName = argv[4]; appesMode = false; } - if (argc == 6) - { - if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5])) - { + if (argc == 6) { + if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5])) { quit(_fail, std::string("Program must be run with the following arguments: ") + " [ [<-appes>]]"); - } - else - { + } else { resultName = argv[4]; appesMode = true; } @@ -4047,137 +4781,214 @@ void registerInteraction(int argc, char* argv[]) quit(_fail, std::string("Can not write to the test-output-file '") + argv[2] + std::string("'")); ouf.init(stdin, _output); - + if (argc >= 4) ans.init(argv[3], _answer); else ans.name = "unopened answer stream"; } -void registerValidation() -{ +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; } -void registerValidation(int argc, char* argv[]) -{ +void registerValidation(int argc, char *argv[]) { registerValidation(); - - for (int i = 1; i < argc; i++) - { - if (!strcmp("--testset", argv[i])) - { + __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 (!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 (!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, testlib_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); } - } + } } -void addFeature(const std::string& feature) -{ +void addFeature(const std::string &feature) { if (testlibMode != _validator) quit(_fail, "Features are supported in validators only."); - validator.addFeature(feature); + validator.addFeature(feature); } -void feature(const std::string& feature) -{ +void feature(const std::string &feature) { if (testlibMode != _validator) quit(_fail, "Features are supported in validators only."); - validator.feature(feature); + validator.feature(feature); } -void registerTestlibCmd(int argc, char* argv[]) -{ - __testlib_ensuresPreconditions(); +class Checker { +private: + bool _initialized; + std::string _testset; + std::string _group; - testlibMode = _checker; - __testlib_set_binary(stdin); +public: + Checker() : _initialized(false), _testset("tests"), _group() { + } - if (argc > 1 && !strcmp("--help", argv[1])) - __testlib_help(); + void initialize() { + _initialized = true; + } - if (argc < 4 || argc > 6) - { - quit(_fail, std::string("Program must be run with the following arguments: ") + - std::string(" [ [<-appes>]]") + - "\nUse \"--help\" to get help information"); + 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; } - if (argc == 4) - { - resultName = ""; - appesMode = false; + 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; } - if (argc == 5) - { - resultName = argv[4]; - appesMode = false; + void setTestset(const char *const testset) { + _testset = testset; } - if (argc == 6) - { - if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5])) - { - quit(_fail, std::string("Program must be run with the following arguments: ") + - " [ [<-appes>]]"); - } - else - { - resultName = argv[4]; + 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); + + 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("[--testset testset] [--group group] [ [<-appes>]]") + + "\nUse \"--help\" to get help information"); + } + + if (argc == 4) { + resultName = ""; + appesMode = false; + } + +#ifndef EJUDGE + if (argc == 5) { + resultName = args[4]; + appesMode = false; + } + + if (argc == 6) { + if ("-APPES" != args[5] && "-appes" != args[5]) { + quit(_fail, std::string("Program must be run with the following arguments: ") + + " [ [<-appes>]]"); + } else { + resultName = args[4]; appesMode = true; } } +#endif - inf.init(argv[1], _input); + inf.init(args[1], _input); #ifdef CMS - ouf.init(argv[3], _output); - ans.init(argv[2], _answer); + ouf.init(args[3], _output); + ans.init(args[2], _answer); #else - ouf.init(argv[2], _output); - ans.init(argv[3], _answer); + ouf.init(args[2], _output); + ans.init(args[3], _answer); #endif + ouf.skipBom(); } -void registerTestlib(int argc, ...) -{ - if (argc < 3 || argc > 5) +void registerTestlib(int argc, ...) { + if (argc < 3 || argc > 5) quit(_fail, std::string("Program must be run with the following arguments: ") + - " [ [<-appes>]]"); + " [ [<-appes>]]"); + + char **argv = new char *[argc + 1]; - char** argv = new char*[argc + 1]; - va_list ap; va_start(ap, argc); argv[0] = NULL; - for (int i = 0; i < argc; i++) - { + for (int i = 0; i < argc; i++) { argv[i + 1] = va_arg(ap, char*); } va_end(ap); @@ -4186,59 +4997,55 @@ void registerTestlib(int argc, ...) delete[] argv; } -static inline void __testlib_ensure(bool cond, const std::string& msg) -{ +static inline void __testlib_ensure(bool cond, const std::string &msg) { if (!cond) quit(_fail, msg.c_str()); } #ifdef __GNUC__ - __attribute__((unused)) +__attribute__((unused)) #endif -static inline void __testlib_ensure(bool cond, const char* msg) -{ +static inline void __testlib_ensure(bool cond, const char *msg) { if (!cond) quit(_fail, msg); } -#define ensure(cond) __testlib_ensure(cond, "Condition failed: \"" #cond "\"") +#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))) #endif -inline void ensuref(bool cond, const char* format, ...) -{ - if (!cond) - { +inline void ensuref(bool cond, const char *format, ...) { + if (!cond) { FMT_TO_RESULT(format, format, message); __testlib_ensure(cond, message); } } -NORETURN static void __testlib_fail(const std::string& message) -{ +NORETURN static void __testlib_fail(const std::string &message) { quitf(_fail, "%s", message.c_str()); } #ifdef __GNUC__ __attribute__ ((format (printf, 1, 2))) #endif -void setName(const char* format, ...) -{ +void setName(const char *format, ...) { FMT_TO_RESULT(format, format, name); checkerName = name; } -/* +/* * Do not use random_shuffle, because it will produce different result * for different C++ compilers. * * This implementation uses testlib random_t to produce random numbers, so * it is stable. - */ + */ template -void shuffle(_RandomAccessIter __first, _RandomAccessIter __last) -{ +void shuffle(_RandomAccessIter __first, _RandomAccessIter __last) { if (__first == __last) return; for (_RandomAccessIter __i = __first + 1; __i != __last; ++__i) std::iter_swap(__i, __first + rnd.next(int(__i - __first) + 1)); @@ -4249,8 +5056,7 @@ template #if defined(__GNUC__) && !defined(__clang__) __attribute__ ((error("Don't use random_shuffle(), use shuffle() instead"))) #endif -void random_shuffle(_RandomAccessIter , _RandomAccessIter ) -{ +void random_shuffle(_RandomAccessIter, _RandomAccessIter) { quitf(_fail, "Don't use random_shuffle(), use shuffle() instead"); } @@ -4261,6 +5067,7 @@ void random_shuffle(_RandomAccessIter , _RandomAccessIter ) #endif #if defined(__GNUC__) && !defined(__clang__) + __attribute__ ((error("Don't use rand(), use rnd.next() instead"))) #endif #ifdef _MSC_VER @@ -4269,58 +5076,40 @@ __attribute__ ((error("Don't use rand(), use rnd.next() instead"))) int rand() RAND_THROW_STATEMENT { quitf(_fail, "Don't use rand(), use rnd.next() instead"); - + /* This line never runs. */ //throw "Don't use rand(), use rnd.next() instead"; } #if defined(__GNUC__) && !defined(__clang__) -__attribute__ ((error("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)."))) + +__attribute__ ((error("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)."))) #endif #ifdef _MSC_VER # pragma warning( disable : 4273 ) #endif 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); + 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=%u].", seed); } -void startTest(int test) -{ +void startTest(int test) { const std::string testFileName = vtos(test); - if (NULL == freopen(testFileName.c_str(), "wt", stdout)) + if (NULL == testlib_freopen_(testFileName.c_str(), "wt", stdout)) __testlib_fail("Unable to write file '" + testFileName + "'"); } -inline std::string upperCase(std::string s) -{ - for (size_t i = 0; i < s.length(); i++) - if ('a' <= s[i] && s[i] <= 'z') - s[i] = char(s[i] - 'a' + 'A'); - return s; -} - -inline std::string lowerCase(std::string s) -{ - for (size_t i = 0; i < s.length(); i++) - if ('A' <= s[i] && s[i] <= 'Z') - s[i] = char(s[i] - 'A' + 'a'); - return s; -} - -inline std::string compress(const std::string& s) -{ +inline std::string compress(const std::string &s) { return __testlib_part(s); } -inline std::string englishEnding(int x) -{ +inline std::string englishEnding(int x) { x %= 100; if (x / 10 == 1) return "th"; @@ -4333,33 +5122,11 @@ inline std::string englishEnding(int x) return "th"; } -inline std::string trim(const std::string& s) -{ - if (s.empty()) - return s; - - int left = 0; - while (left < int(s.length()) && isBlanks(s[left])) - left++; - if (left >= int(s.length())) - return ""; - - int right = int(s.length()) - 1; - while (right >= 0 && isBlanks(s[right])) - right--; - if (right < 0) - return ""; - - return s.substr(left, right - left + 1); -} - -template -std::string join(_ForwardIterator first, _ForwardIterator last, _Separator separator) -{ +template +std::string join(_ForwardIterator first, _ForwardIterator last, _Separator separator) { std::stringstream ss; bool repeated = false; - for (_ForwardIterator i = first; i != last; i++) - { + for (_ForwardIterator i = first; i != last; i++) { if (repeated) ss << separator; else @@ -4369,39 +5136,33 @@ std::string join(_ForwardIterator first, _ForwardIterator last, _Separator separ return ss.str(); } -template -std::string join(_ForwardIterator first, _ForwardIterator last) -{ +template +std::string join(_ForwardIterator first, _ForwardIterator last) { return join(first, last, ' '); } -template -std::string join(const _Collection& collection, _Separator separator) -{ +template +std::string join(const _Collection &collection, _Separator separator) { return join(collection.begin(), collection.end(), separator); } -template -std::string join(const _Collection& collection) -{ +template +std::string join(const _Collection &collection) { return join(collection, ' '); } /** * Splits string s by character separator returning exactly k+1 items, - * where k is the number of separator occurences. - */ -std::vector split(const std::string& s, char separator) -{ + * where k is the number of separator occurrences. + */ +std::vector split(const std::string &s, char separator) { std::vector result; std::string item; for (size_t i = 0; i < s.length(); i++) - if (s[i] == separator) - { + if (s[i] == separator) { result.push_back(item); item = ""; - } - else + } else item += s[i]; result.push_back(item); return result; @@ -4409,26 +5170,23 @@ std::vector split(const std::string& s, char separator) /** * Splits string s by character separators returning exactly k+1 items, - * where k is the number of separator occurences. - */ -std::vector split(const std::string& s, const std::string& separators) -{ + * where k is the number of separator occurrences. + */ +std::vector split(const std::string &s, const std::string &separators) { if (separators.empty()) return std::vector(1, s); std::vector isSeparator(256); for (size_t i = 0; i < separators.size(); i++) - isSeparator[(unsigned char)(separators[i])] = true; + isSeparator[(unsigned char) (separators[i])] = true; std::vector result; std::string item; for (size_t i = 0; i < s.length(); i++) - if (isSeparator[(unsigned char)(s[i])]) - { + if (isSeparator[(unsigned char) (s[i])]) { result.push_back(item); item = ""; - } - else + } else item += s[i]; result.push_back(item); return result; @@ -4436,19 +5194,16 @@ std::vector split(const std::string& s, const std::string& separato /** * Splits string s by character separator returning non-empty items. - */ -std::vector tokenize(const std::string& s, char separator) -{ + */ +std::vector tokenize(const std::string &s, char separator) { std::vector result; std::string item; for (size_t i = 0; i < s.length(); i++) - if (s[i] == separator) - { + if (s[i] == separator) { if (!item.empty()) result.push_back(item); item = ""; - } - else + } else item += s[i]; if (!item.empty()) result.push_back(item); @@ -4457,148 +5212,140 @@ std::vector tokenize(const std::string& s, char separator) /** * Splits string s by character separators returning non-empty items. - */ -std::vector tokenize(const std::string& s, const std::string& separators) -{ + */ +std::vector tokenize(const std::string &s, const std::string &separators) { if (separators.empty()) return std::vector(1, s); std::vector isSeparator(256); for (size_t i = 0; i < separators.size(); i++) - isSeparator[(unsigned char)(separators[i])] = true; + isSeparator[(unsigned char) (separators[i])] = true; std::vector result; std::string item; for (size_t i = 0; i < s.length(); i++) - if (isSeparator[(unsigned char)(s[i])]) - { + if (isSeparator[(unsigned char) (s[i])]) { if (!item.empty()) result.push_back(item); item = ""; - } - else + } else item += s[i]; - + if (!item.empty()) result.push_back(item); return result; } -NORETURN void __testlib_expectedButFound(TResult result, std::string expected, std::string found, const char* prepend) -{ +NORETURN void __testlib_expectedButFound(TResult result, std::string expected, std::string found, const char *prepend) { std::string message; if (strlen(prepend) != 0) - message = format("%s: expected '%s', but found '%s'", - compress(prepend).c_str(), compress(expected).c_str(), compress(found).c_str()); + message = testlib_format_("%s: expected '%s', but found '%s'", + compress(prepend).c_str(), compress(expected).c_str(), compress(found).c_str()); else - message = format("expected '%s', but found '%s'", - compress(expected).c_str(), compress(found).c_str()); + message = testlib_format_("expected '%s', but found '%s'", + compress(expected).c_str(), compress(found).c_str()); quit(result, message); } -NORETURN void __testlib_expectedButFound(TResult result, double expected, double found, const char* prepend) -{ - std::string expectedString = removeDoubleTrailingZeroes(format("%.12f", expected)); - std::string foundString = removeDoubleTrailingZeroes(format("%.12f", found)); +NORETURN void __testlib_expectedButFound(TResult result, double expected, double found, const char *prepend) { + std::string expectedString = removeDoubleTrailingZeroes(testlib_format_("%.12f", expected)); + std::string foundString = removeDoubleTrailingZeroes(testlib_format_("%.12f", found)); __testlib_expectedButFound(result, expectedString, foundString, prepend); } -template +template #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, T expected, T found, const char* prependFormat = "", ...) -{ +NORETURN void expectedButFound(TResult result, T expected, T found, const char *prependFormat = "", ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); std::string expectedString = vtos(expected); std::string foundString = vtos(found); __testlib_expectedButFound(result, expectedString, foundString, prepend.c_str()); } -template <> +template<> #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, std::string expected, std::string found, const char* prependFormat, ...) -{ +NORETURN void +expectedButFound(TResult result, std::string expected, std::string found, const char *prependFormat, ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); __testlib_expectedButFound(result, expected, found, prepend.c_str()); } -template <> +template<> #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, double expected, double found, const char* prependFormat, ...) -{ +NORETURN void expectedButFound(TResult result, double expected, double found, const char *prependFormat, ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); - std::string expectedString = removeDoubleTrailingZeroes(format("%.12f", expected)); - std::string foundString = removeDoubleTrailingZeroes(format("%.12f", found)); + std::string expectedString = removeDoubleTrailingZeroes(testlib_format_("%.12f", expected)); + std::string foundString = removeDoubleTrailingZeroes(testlib_format_("%.12f", found)); __testlib_expectedButFound(result, expectedString, foundString, prepend.c_str()); } -template <> +template<> #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, const char* expected, const char* found, const char* prependFormat, ...) -{ +NORETURN void +expectedButFound(TResult result, const char *expected, const char *found, const char *prependFormat, + ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); __testlib_expectedButFound(result, std::string(expected), std::string(found), prepend.c_str()); } -template <> +template<> #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, float expected, float found, const char* prependFormat, ...) -{ +NORETURN void expectedButFound(TResult result, float expected, float found, const char *prependFormat, ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); __testlib_expectedButFound(result, double(expected), double(found), prepend.c_str()); } -template <> +template<> #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, long double expected, long double found, const char* prependFormat, ...) -{ +NORETURN void +expectedButFound(TResult result, long double expected, long double found, const char *prependFormat, ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); __testlib_expectedButFound(result, double(expected), double(found), prepend.c_str()); } #if __cplusplus > 199711L || defined(_MSC_VER) -template -struct is_iterable -{ - template - static char test(typename U::iterator* x); - - template - static long test(U* x); - +template +struct is_iterable { + template + static char test(typename U::iterator *x); + + template + static long test(U *x); + static const bool value = sizeof(test(0)) == 1; }; template -struct __testlib_enable_if {}; - +struct __testlib_enable_if { +}; + template -struct __testlib_enable_if { typedef T type; }; +struct __testlib_enable_if { + typedef T type; +}; -template -typename __testlib_enable_if::value, void>::type __testlib_print_one(const T& t) -{ +template +typename __testlib_enable_if::value, void>::type __testlib_print_one(const T &t) { std::cout << t; } - -template -typename __testlib_enable_if::value, void>::type __testlib_print_one(const T& t) -{ + +template +typename __testlib_enable_if::value, void>::type __testlib_print_one(const T &t) { bool first = true; - for (typename T::const_iterator i = t.begin(); i != t.end(); i++) - { + for (typename T::const_iterator i = t.begin(); i != t.end(); i++) { if (first) first = false; else @@ -4608,17 +5355,15 @@ typename __testlib_enable_if::value, void>::type __testlib_print_ } template<> -typename __testlib_enable_if::value, void>::type __testlib_print_one(const std::string& t) -{ +typename __testlib_enable_if::value, void>::type +__testlib_print_one(const std::string &t) { std::cout << t; } - + template -void __println_range(A begin, B end) -{ +void __println_range(A begin, B end) { bool first = true; - for (B i = B(begin); i != end; i++) - { + for (B i = B(begin); i != end; i++) { if (first) first = false; else @@ -4629,46 +5374,47 @@ void __println_range(A begin, B end) } template -struct is_iterator -{ +struct is_iterator { static T makeT(); - typedef void * twoptrs[2]; - static twoptrs & test(...); - template static typename R::iterator_category * test(R); - template static void * test(R *); - static const bool value = sizeof(test(makeT())) == sizeof(void *); + + typedef void *twoptrs[2]; + + static twoptrs &test(...); + + template + static typename R::iterator_category *test(R); + + template + static void *test(R *); + + static const bool value = sizeof(test(makeT())) == sizeof(void *); }; template -struct is_iterator::value >::type> -{ - static const bool value = false; +struct is_iterator::value>::type> { + static const bool value = false; }; -template -typename __testlib_enable_if::value, void>::type println(const A& a, const B& b) -{ +template +typename __testlib_enable_if::value, void>::type println(const A &a, const B &b) { __testlib_print_one(a); std::cout << " "; __testlib_print_one(b); std::cout << std::endl; } - -template -typename __testlib_enable_if::value, void>::type println(const A& a, const B& b) -{ + +template +typename __testlib_enable_if::value, void>::type println(const A &a, const B &b) { __println_range(a, b); } -template -void println(const A* a, const A* b) -{ +template +void println(const A *a, const A *b) { __println_range(a, b); } -template <> -void println(const char* a, const char* b) -{ +template<> +void println(const char *a, const char *b) { __testlib_print_one(a); std::cout << " "; __testlib_print_one(b); @@ -4676,15 +5422,13 @@ void println(const char* a, const char* b) } template -void println(const T& x) -{ +void println(const T &x) { __testlib_print_one(x); std::cout << std::endl; } template -void println(const A& a, const B& b, const C& c) -{ +void println(const A &a, const B &b, const C &c) { __testlib_print_one(a); std::cout << " "; __testlib_print_one(b); @@ -4694,8 +5438,7 @@ void println(const A& a, const B& b, const C& c) } template -void println(const A& a, const B& b, const C& c, const D& d) -{ +void println(const A &a, const B &b, const C &c, const D &d) { __testlib_print_one(a); std::cout << " "; __testlib_print_one(b); @@ -4707,8 +5450,7 @@ void println(const A& a, const B& b, const C& c, const D& d) } template -void println(const A& a, const B& b, const C& c, const D& d, const E& e) -{ +void println(const A &a, const B &b, const C &c, const D &d, const E &e) { __testlib_print_one(a); std::cout << " "; __testlib_print_one(b); @@ -4722,8 +5464,7 @@ void println(const A& a, const B& b, const C& c, const D& d, const E& e) } template -void println(const A& a, const B& b, const C& c, const D& d, const E& e, const F& f) -{ +void println(const A &a, const B &b, const C &c, const D &d, const E &e, const F &f) { __testlib_print_one(a); std::cout << " "; __testlib_print_one(b); @@ -4739,8 +5480,7 @@ void println(const A& a, const B& b, const C& c, const D& d, const E& e, const F } template -void println(const A& a, const B& b, const C& c, const D& d, const E& e, const F& f, const G& g) -{ +void println(const A &a, const B &b, const C &c, const D &d, const E &e, const F &f, const G &g) { __testlib_print_one(a); std::cout << " "; __testlib_print_one(b); @@ -4756,5 +5496,831 @@ void println(const A& a, const B& b, const C& c, const D& d, const E& e, const F __testlib_print_one(g); std::cout << std::endl; } + +/* opts */ + +/** + * 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 0; + + if (s[0] == '-') { + if (isalpha(s[1])) + return 1; + else if (s[1] == '-') + return isalpha(s[2]) ? 2 : 0; + } + + return 0; +} + +/** + * 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; + + size_t type = getOptType(argv[index]), inc = 1; + if (type > 0) { + std::string key(argv[index] + type), val; + size_t sep = key.find('='); + if (sep != std::string::npos) { + val = key.substr(sep + 1); + key = key.substr(0, sep); + } else { + if (index + 1 < argc && getOptType(argv[index + 1]) == 0) { + val = argv[index + 1]; + inc = 2; + } else { + if (key.length() > 1 && isdigit(key[1])) { + val = key.substr(1); + key = key.substr(0, 1); + } else { + val = "true"; + } + } + } + opts[key].value = val; + } else { + return inc; + } + + return inc; +} + +/** + * Global list containing all the arguments in the order given in the command line. + */ +std::vector __testlib_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(); + 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()) + ")"); + return __testlib_argv[size_t(index)]; +} + +/** + * 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) + "'"); + it->second.used = true; + return it->second.value; +} + +template +T optValueToIntegral(const std::string &s, bool nonnegative); + +long double optValueToLongDouble(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 != std::string::npos) + __testlib_fail("Opts: expected typical exponential notation but '" + compress(s) + "' found"); + pos = i; + } + if (pos == std::string::npos) + return s; + std::string e = s.substr(pos + 1); + if (!e.empty() && e[0] == '+') + e = e.substr(1); + if (e.empty()) + __testlib_fail("Opts: expected typical exponential notation but '" + compress(s) + "' found"); + if (e.length() > 20) + __testlib_fail("Opts: expected typical exponential notation but '" + compress(s) + "' found"); + int ne = optValueToIntegral(e, false); + std::string num = s.substr(0, pos); + if (num.length() > 20) + __testlib_fail("Opts: expected typical exponential notation but '" + compress(s) + "' found"); + if (!num.empty() && num[0] == '+') + num = num.substr(1); + optValueToLongDouble(num); + bool minus = false; + if (num[0] == '-') { + minus = true; + num = num.substr(1); + } + for (int i = 0; i < +ne; i++) { + size_t sep = num.find('.'); + if (sep == std::string::npos) + num += '0'; + else { + if (sep + 1 == num.length()) + num[sep] = '0'; + else + std::swap(num[sep], num[sep + 1]); + } + } + for (int i = 0; i < -ne; i++) { + size_t sep = num.find('.'); + if (sep == std::string::npos) + num.insert(num.begin() + int(num.length()) - 1, '.'); + else { + if (sep == 0) + num.insert(num.begin() + 1, '0'); + else + std::swap(num[sep - 1], num[sep]); + } + } + while (!num.empty() && num[0] == '0') + num = num.substr(1); + while (num.find('.') != std::string::npos && num.back() == '0') + num = num.substr(0, num.length() - 1); + if (!num.empty() && num.back() == '.') + num = num.substr(0, num.length() - 1); + if ((!num.empty() && num[0] == '.') || num.empty()) + num.insert(num.begin(), '0'); + return (minus ? "-" : "") + num; +} + +template +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"); + T value = 0; + long double about = 0.0; + signed char sign = +1; + size_t pos = 0; + if (s[pos] == '-') { + if (nonnegative) + __testlib_fail("Opts: expected non-negative integer but '" + compress(s_) + "' found"); + sign = -1; + pos++; + } + for (size_t i = pos; i < s.length(); i++) { + if (s[i] < '0' || s[i] > '9') + __testlib_fail("Opts: expected integer but '" + compress(s_) + "' found"); + value = T(value * 10 + s[i] - '0'); + about = about * 10 + s[i] - '0'; + } + value *= sign; + about *= sign; + if (fabsl(value - about) > 0.1) + __testlib_fail("Opts: integer overflow: expected integer but '" + compress(s_) + "' found"); + return value; +} + +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"); + long double value = 0.0; + signed char sign = +1; + size_t pos = 0; + if (s[pos] == '-') { + sign = -1; + pos++; + } + bool period = false; + long double mul = 1.0; + for (size_t i = pos; i < s.length(); i++) { + if (s[i] == '.') { + if (period) + __testlib_fail("Opts: expected float number but '" + compress(s_) + "' found"); + else { + period = true; + continue; + } + } + if (period) + mul *= 10.0; + if (s[i] < '0' || s[i] > '9') + __testlib_fail("Opts: expected float number but '" + compress(s_) + "' found"); + if (period) + value += (s[i] - '0') / mul; + else + value = value * 10 + s[i] - '0'; + } + value *= sign; + 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 is_floating_point, int index); + +template<> +std::string opt(std::false_type /*is_floating_point*/, int index) { + return __testlib_indexToArgv(index); +} + +template +T opt(std::true_type /*is_floating_point*/, int index) { + return T(optValueToLongDouble(__testlib_indexToArgv(index))); +} + +template +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 /*is_integral*/, std::false_type /*is_unsigned*/, int index) { + return optValueToIntegral(__testlib_indexToArgv(index), false); +} + +template +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 /*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"); +} + +/** + * 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 is_floating_point, const std::string &key); + +template<> +std::string opt(std::false_type /*is_floating_point*/, const std::string &key) { + return __testlib_keyToOpts(key); +} + +template +T opt(std::true_type /*is_integral*/, const std::string &key) { + return T(optValueToLongDouble(__testlib_keyToOpts(key))); +} + +template +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 /*is_integral*/, std::false_type /*is_unsigned*/, const std::string &key) { + return optValueToIntegral(__testlib_keyToOpts(key), false); +} + +template +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 /*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"); +} + +/** + * Return the parsed opt by a given key. + */ +template +T opt(const std::string &key) { + return opt(std::is_integral(), std::is_unsigned(), 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; +#ifdef _MSC_VER + ensuref(sscanf_s(s.c_str(), "%lf", &result) == 1, "Invalid serialized points"); +#else + ensuref(std::sscanf(s.c_str(), "%lf", &result) == 1, "Invalid serialized points"); +#endif + 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(testlib_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 + +#ifdef __GNUC__ +__attribute__ ((format (printf, 1, 2))) +#endif +std::string testlib_format_(const char *fmt, ...) { + FMT_TO_RESULT(fmt, fmt, result); + return result; +} + +std::string testlib_format_(const std::string fmt, ...) { + FMT_TO_RESULT(fmt, fmt.c_str(), result); + return result; +} + +#if (__cplusplus >= 202002L && __has_include()) || __cpp_lib_format +template +std::string format(const char* fmt, Args&&... args) { + size_t size = size_t(std::snprintf(nullptr, 0, fmt, args...) + 1); + std::vector buffer(size); + std::snprintf(buffer.data(), size, fmt, args...); + return std::string(buffer.data()); +} + +template +std::string format(const std::string fmt, Args&&... args) { + size_t size = size_t(std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1); + std::vector buffer(size); + std::snprintf(buffer.data(), size, fmt.c_str(), args...); + return std::string(buffer.data()); +} +#else +#ifdef __GNUC__ +__attribute__ ((format (printf, 1, 2))) +#endif +std::string format(const char *fmt, ...) { + FMT_TO_RESULT(fmt, fmt, result); + return result; +} + +std::string format(const std::string fmt, ...) { + FMT_TO_RESULT(fmt, fmt.c_str(), result); + return result; +} +#endif + #endif