diff --git a/example_package/.gitignore b/example_package/.gitignore new file mode 100644 index 00000000..1e728bc9 --- /dev/null +++ b/example_package/.gitignore @@ -0,0 +1,76 @@ +# sinol-make +.cache +cache +in/.md5sums + +# Tests +in/*.in +out/*.out +!in/???0*.in +!out/???0*.out + +# export package file +*.tgz + +# LaTeX +*.pdf +*.ps +!doc/logo.* + +*.aux +*.lof +*.log +*.lot +*.fls +doc/*.out +*.toc +*.fmt +*.fot +*.cb +*.cb2 +.*.lb + +*.dvi +*.xdv +*-converted-to.* +# these rules might exclude image files for figures etc. +*.eps + +## Bibliography auxiliary files (bibtex/biblatex/biber): +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.run.xml + +# Encrypted files +*.gpg + +# SIO binnary +*.e + +# Python Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# pyenv +.python-version + +# IPython +profile_default/ +ipython_config.py + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so +*.o diff --git a/examples/config.yml b/example_package/config.yml similarity index 94% rename from examples/config.yml rename to example_package/config.yml index 21baee08..b6228d8c 100644 --- a/examples/config.yml +++ b/example_package/config.yml @@ -13,10 +13,8 @@ title_pl: Przykładowe zadanie # (if number of groups doesn't divide 100, then the last groups will have the remaining points). # Group 0 always has zero points. scores: - 1: 20 - 2: 30 - 3: 25 - 4: 25 + 1: 40 + 2: 60 # Time limit for all tests is defined in `time_limit` key. # More precise time limit for each group or test can be defined in `time_limits` key. @@ -43,18 +41,22 @@ override_limits: # Extra compilation arguments can be defined in `extra_compile_args` key. # Each language can have different extra arguments. -extra_compilation_args: - cpp: 'abclib.cpp' + +# extra_compilation_args: +# cpp: 'abclib.cpp' + # The arguments can also be in an array: -extra_compilation_args: - cpp: - - 'abclib.cpp' - - 'abclib2.cpp' + +# extra_compilation_args: +# cpp: +# - 'abclib.cpp' +# - 'abclib2.cpp' # Additional files used in compilation can be defined in `extra_compilation_files` key. # They are copied to the directory where the source code is compiled. # All languages have the same additional files. -extra_compilation_files: ['abclib.cpp', 'abclib.py'] + +# extra_compilation_files: ['abclib.cpp', 'abclib.py'] ### Keys used by sinol-make: diff --git a/example_package/in/.gitkeep b/example_package/in/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/example_package/out/.gitkeep b/example_package/out/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/example_package/prog/abc.cpp b/example_package/prog/abc.cpp new file mode 100644 index 00000000..2d1f3b2f --- /dev/null +++ b/example_package/prog/abc.cpp @@ -0,0 +1,12 @@ +// This is the main model solution. +// It is used for generating output files. + +#include + +using namespace std; + +int main() { + int a, b; + cin >> a >> b; + cout << a + b << endl; +} diff --git a/example_package/prog/abcingen.cpp b/example_package/prog/abcingen.cpp new file mode 100644 index 00000000..b5a0f7db --- /dev/null +++ b/example_package/prog/abcingen.cpp @@ -0,0 +1,45 @@ +#include +#include "oi.h" +using namespace std; + +// Change this function to generate one test for stresstesting. +// The script prog/abcingen.sh in 10 seconds generates +// as much tests as possible and compares the outputs +// of the model solution and brute solution. +// The tests shouldn't be very big, but should be able to cover edge cases. +void generate_one_stresstest(oi::Random &rng) { + cout << rng.randSInt(1, 10) << ' ' << rng.randSInt(1, 10) << endl; +} + +// Change this function to create a test with the given name. +// The lists of tests to generate needs to be written in prog/abcingen.sh +void generate_proper_test(string test_name, oi::Random &rng) { + if (test_name == "0a") + cout << "0 1" << endl; + else if (test_name == "1a") + cout << rng.randSInt(5, 1'000) << ' ' << rng.randSInt(5, 1'000) << endl; + else if (test_name == "2a") + cout << "2 2" << endl; + else { + cerr << "Unrecognized test_name = " << test_name << endl; + exit(1); + } +} + +int main(int argc, char *argv[]) { + if (argc == 3 && string(argv[1]) == "stresstest") { + unsigned int seed = atoi(argv[2]); + oi::Random rng(seed); + generate_one_stresstest(rng); + return 0; + } + if (argc != 2) { + cerr << "Run prog/abcingen.sh to stresstest and create proper tests." << endl; + exit(1); + } + string test_name = argv[1]; + unsigned int seed = (unsigned int) hash{}(test_name); + oi::Random rng(seed); + cerr << "Generating test " << test_name << "..." << endl; + generate_proper_test(test_name, rng); +} diff --git a/example_package/prog/abcingen.sh b/example_package/prog/abcingen.sh new file mode 100644 index 00000000..ad7b6e87 --- /dev/null +++ b/example_package/prog/abcingen.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# This script first stresstests the model solution for 10 seconds +# and if it passes, it will generate the proper tests. +# To generate both types of tests, it executes ingen.cpp and passes it some arguments. +# The `test_ids` variable needs to have a manually written list of all proper tests. + +prog_dir="$(realpath "$(dirname "$0")")" +cache_dir="$prog_dir/../.cache" +mkdir -p "$cache_dir" +script_name="$(basename "$0")" +task_id=${script_name:0:3} +gen_exe="$cache_dir/${task_id}ingen" +sol_exe="$cache_dir/${task_id}solution" +slo_exe="$cache_dir/${task_id}slow" +stresstest_seconds=10 +function compile_cpp { + g++ -std=c++20 -O3 -lm -Werror -Wall -Wextra -Wshadow -Wconversion -Wno-unused-result -Wfloat-equal "$1" -o "$2" \ + || exit 1 +} + +# Change the list of tests to generate and (if needed) the paths of solutions. +test_ids="0a 1a 2a" +compile_cpp "$prog_dir/${task_id}ingen.cpp" "$gen_exe" +compile_cpp "$prog_dir/${task_id}.cpp" "$sol_exe" +compile_cpp "$prog_dir/${task_id}s.cpp" "$slo_exe" + +for (( i=0, SECONDS=0; SECONDS < stresstest_seconds; i++ )); do + in_test="$cache_dir/input.in" + slo_out="$cache_dir/slo.out" + sol_out="$cache_dir/sol.out" + printf "Running stresstest $i\r" + "$gen_exe" stresstest $i > "$in_test" || { echo "Failed to generate test $i"; exit 1; } + "$slo_exe" < "$in_test" > "$slo_out" || { echo "Brute crashed on test $i"; exit 1; } + "$sol_exe" < "$in_test" > "$sol_out" || { echo "Solution crashed on test $i"; exit 1; } + diff "$slo_out" "$sol_out" -w > /dev/null || { echo "Outputs differ on test $i"; exit 1; } +done +echo "Stresstest passed with $i tests" + +for test in $test_ids; do + "$gen_exe" "$test" > "$prog_dir/../in/${task_id}${test}.in" || { echo "Failed to generate test $test"; exit 1; } +done diff --git a/example_package/prog/abcinwer.cpp b/example_package/prog/abcinwer.cpp new file mode 100644 index 00000000..2bfb3a5f --- /dev/null +++ b/example_package/prog/abcinwer.cpp @@ -0,0 +1,56 @@ +#include +#include "oi.h" +using namespace std; + +int main() { + oi::Scanner in(stdin, oi::PL); + + // Change this code to read and validate the input. + int n = in.readInt(0, 1'000); + in.readSpace(); + int m = in.readInt(0, 1'000); + in.readEoln(); + in.readEof(); + assert(n > 0 || m > 0); + + // Change this code to have functions which return + // whether the test satisfies a given subtask. + auto is_subtask1 = [&]() -> bool { + return n >= 0 && m >= 0; + }; + auto is_subtask2 = [&]() -> bool { + return n >= 5 && m >= 5; + }; + + // Change this code to have functions which return + // whether the test is exactly the same as + // the sample tests in the statement. + auto is_0a = [&]() -> bool { + return n == 0 && m == 1; + }; + auto is_1ocen = [&]() -> bool { + return n == 1000 && m == 1000; + }; + + map subtasks = { + {1, is_subtask1()}, + {2, is_subtask2()}, + }; + string subtasks_s; + for (auto [subtask_id, is_valid] : subtasks) + subtasks_s += is_valid ? to_string(subtask_id) : string("-"); + + map sample_tests = { + {"0a", is_0a()}, + {"1ocen", is_1ocen()}, + }; + string sample_test_s = "-"; + for (auto [name, is_valid] : sample_tests) + if (is_valid) + sample_test_s = name; + + cout << "OK " + << "n = " << setw(4) << n << ", " + << "m = " << setw(4) << m << ", " + << "subtasks = " << subtasks_s << ", sample test = " << sample_test_s << endl; +} diff --git a/example_package/prog/abcs.cpp b/example_package/prog/abcs.cpp new file mode 100644 index 00000000..b25a5dff --- /dev/null +++ b/example_package/prog/abcs.cpp @@ -0,0 +1,11 @@ +// This is a "brute force" solution for testing model solution. + +#include + +using namespace std; + +int main() { + int a, b; + cin >> a >> b; + cout << a + b << endl; +} diff --git a/example_package/prog/oi.h b/example_package/prog/oi.h new file mode 100644 index 00000000..3dc11bd8 --- /dev/null +++ b/example_package/prog/oi.h @@ -0,0 +1,651 @@ +/* + oi.h - pakiet funkcji do pisania weryfikatorow wejsc (inwer) i wyjsc (chk) + Pierwotny autor: Piotr Niedzwiedz + W razie problemow, bledow lub pomyslow na ulepszenie prosze pisac issues: https://sinol3.dasie.mimuw.edu.pl/sinol3/template-package +*/ + +#ifndef OI_LIB_OI_H_ +#define OI_LIB_OI_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +using std::vector; +using std::max; +using std::swap; + +// We want prevent usage of standard random function. +int rand(){ + fprintf(stderr, "DONT USE rand or random_shuffle!\nUse oi::Random class.\n"); + exit(1); + return 0; +} + +namespace oi { + +enum Lang { + EN = 0, + PL = 1 +}; + +class Reader; + +class Scanner { + protected: + static const int realNumbersLimit = 20; + + Lang lang; + Reader* reader; + void(*end)(const char* msg, int line, int position); + + void readULL(unsigned long long int limit, unsigned long long int &val, bool &sign); + void readLDB(long double &val, bool &sign); + + public: + Scanner(const char* file, Lang _lang = Lang(EN)); + Scanner(const char* file, void(*endf)(const char* msg, int line, int position), Lang _lang = Lang(EN)); + Scanner(FILE* input, Lang _lang = Lang(EN)); + Scanner(FILE* input, void(*endf)(const char* msg, int line, int position), Lang _lang = Lang(EN)); + ~Scanner(); + + void error(const char* msg); + + // Skips all whitespaces until any occurrence of EOF or other non-white character. + int skipWhitespaces(); + // Skips all whitespaces until any occurrence of EOF, EOLN or other non-white character. + int skipWhitespacesUntilEOLN(); + + int readInt(int min_value = INT_MIN, int max_value = INT_MAX); + unsigned int readUInt(unsigned int min_value = 0, unsigned int max_value = UINT_MAX); + long long readLL(long long int min_value = LLONG_MIN, long long int max_value = LLONG_MAX); + unsigned long long readULL(unsigned long long int min_value = 0, unsigned long long int max_value = ULLONG_MAX); + + float readFloat(float min_value, float max_value); + double readDouble(double min_value, double max_value); + long double readLDouble(long double min_value, long double max_value); + + char readChar(); + + // Newline character is read, but isn't added to s + int readLine(char* s, int size); + + // Reads a string until occurrence of EOF, EOLN or whitespace. + // Returns the number of characters read (possibly 0). + int readString(char* s, int size); + + void readEof(); + void readEofOrEoln(); + void readEoln(); + void readSpace(); + void readTab(); + + bool isEOF(); + private: + Scanner(Scanner&) {} +}; + +class MultipleRecursiveGenerator; + +class Random { + public: + Random(); + explicit Random(unsigned int seed); + ~Random(); + void setSeed(unsigned int seed); + + // Random number from range <0..2^31 - 1> + int rand(); + + // randS* - signed * + // randU* - unsigned * + int randSInt(); + unsigned int randUInt(); + long long randSLL(); + unsigned long long randULL(); + + /** + * Generuje liczbe pseudo-losowa z przedzialu [a..b] (obustronnie wlacznie). + * Podanie na wejsciu pustego przedzialu (b < a) jest bledem i skutkuje + * dzialaniem niezdefiniowanym. + */ + int randSInt(const int a, const int b); + + template + void randomShuffle(RandomAccessIterator first, RandomAccessIterator last); + + private: + Random(Random&) {} + MultipleRecursiveGenerator* mrg_; + void init(); +}; + +class MultipleRecursiveGenerator { + public: + MultipleRecursiveGenerator(unsigned int modulo, + const vector &A); + void setSeed(unsigned int seed); + + /** + * Generuje liczbe pseudo-losowa z przedzialu [0..modulo-1]. Chcemy zeby to + * bylo co najmniej 16 bitow, czyli musi byc (0xFFFF < modulo). Gwarantuje + * to asercja w konstruktorze klasy. + */ + unsigned int next16Bits(); + + private: + long int n_; + unsigned int modulo_; + vector A_; + vector X_; +}; + +class Reader { + private: + static const int bufferSize = 1000; + char Buffer[bufferSize]; + int head, tail; + int line, position; + void fillBuffer(); + FILE* input; + public: + explicit Reader(const char* file); + explicit Reader(FILE* _input); + ~Reader(); + bool isEOF(); + int getLine() {return line;} + int getPosition() {return position;} + char read(bool move = false); + private: + Reader(Reader&) {}; +}; + + +const char* msgLeadingZeros[]= { + "Leading zeros", + "Zera wiodace"}; +const char* msgMinusZero[]= { + "Minus zero -0", + "Minus zero -0"}; +const char* msgNoNumber[]= { + "No number", + "Brak liczby"}; +const char* msgNoChar[]= { + "No char - EOF", + "Brak znaku - EOF"}; +const char* msgNotEof[]= { + "Not EOF", + "Brak konca pliku"}; +const char* msgNotEoln[]= { + "Not EOLN", + "Brak konca linii"}; +const char* msgNotEofOrEoln[]= { + "Not EOF or EOLN", + "Brak konca linii i brak konca pliku"}; +const char* msgNotSpace[]= { + "Not space", + "Brak spacji"}; +const char* msgNotTab[]= { + "Not tab", + "Brak znaku tabulacji"}; +const char* msgOutOfRangeInt[]= { + "Integer out of range", + "Liczba calkowita spoza zakresu"}; +const char* msgOutOfRangeReal[]= { + "Real number out of range", + "Liczba rzeczywista spoza zakresu"}; +const char* msgRealNumberLimit[]= { + "Too many digits after dot", + "Za duzo cyfr po kropce dziesietnej"}; +const char* msgBadRealNumberFormat[]= { + "Bad real number format", + "Niepoprawny format liczby rzeczywistej"}; + +// ------------------------------- Implementation ----------------------------- + +typedef unsigned long long ull; +typedef unsigned int uint; +typedef long long ll; +typedef long double ldb; + + +inline bool isDot(char x) { + return x == '.'; +} + +inline bool isEOLN(char x) { + return x == '\n'; +} + +inline bool isMinus(char x) { + return x == '-'; +} + +inline bool isSpace(char x) { + return x == ' '; +} + +inline bool isTab(char x) { + return x == '\t'; +} + +inline bool isWhitespace(char x) { + return x == ' ' || x == '\t' || x == '\n'; +} + +void endDefault(const char* msg, int line, int position) { + printf("ERROR(line: %d, position: %d): %s\n", line, position, msg); + exit(1); +} + +// ------------------------------- Random ------------------------------------- + +void Random::init() { +// Here is a reference about it: +// http://random.mat.sbg.ac.at/results/karl/server/node7.html + vector A(5, 0); + static_assert(4<=sizeof(int), "Typ int musi miescic co najmniej 4 bajty."); + static_assert(0x7FFFFFFFLL<=INT_MAX, "Wartosc 2^31-1 musi miescic sie w typie int."); + A[0] = 107374182, A[4] = 104480; + unsigned int modulo = 2147483647; // 2^31 -1 + mrg_ = new MultipleRecursiveGenerator(modulo, A); +} + +Random::Random() { + init(); +} + +Random::Random(unsigned int seed) { + init(); + setSeed(seed); +} + +Random::~Random() { + delete mrg_; +} + +void Random::setSeed(unsigned int seed) { + mrg_->setSeed(seed); +} + +static_assert(((8==CHAR_BIT) && (UCHAR_MAX==0xFF)), "Wiecej niz 8 bitow w bajcie? Nie wspierane, sorry."); +#define RAND_TYPE(type)\ + type res = 0;\ + for (size_t i = 0; i < sizeof(type)/2; ++i) {\ + res |= (((type)mrg_->next16Bits()) & (0xFFFF)) << (i * 16);\ + }\ + return res; + + +int Random::rand() { + int x = randSInt(); + if (x<0) return ~x; + return x; +} + +int Random::randSInt(const int a, const int b) { + return (rand() % (b-a+1)) + a; +} + +int Random::randSInt() { + RAND_TYPE(int); +} + +unsigned int Random::randUInt() { + RAND_TYPE(unsigned int); +} + +long long Random::randSLL() { + RAND_TYPE(long long); +} + +unsigned long long Random::randULL() { + RAND_TYPE(unsigned long long); +} + +template +void Random::randomShuffle(RandomAccessIterator first, RandomAccessIterator last) { + long int n = last - first; + for (int i = 1; i < n; ++i) { + int to = rand() % (i+1); + swap(first[to], first[i]); + } +} + +// ----------------------- MultipleRecursiveGenerator ------------------------- + +MultipleRecursiveGenerator::MultipleRecursiveGenerator( + unsigned int modulo, + const vector &A) : modulo_(modulo), A_(A) { + assert(0xFFFFUL < modulo); + n_ = A_.size(); + X_ = vector(n_, 0); + setSeed(0); +} + +void MultipleRecursiveGenerator::setSeed(unsigned int seed) { + for (int i = 0; i < n_; ++i) { + seed = (seed + 1) % modulo_; + X_[i] = seed; + } + for (int i = 0; i < n_; ++i) next16Bits(); +} + +unsigned int MultipleRecursiveGenerator::next16Bits() { + unsigned int res = 0; + static_assert(2 * sizeof(unsigned int) <= sizeof(unsigned long long), "Mozliwe przepelnienie arytmetyczne!"); + for (int i = 0; i < n_; ++i) { + res = (unsigned int)((res + (unsigned long long)A_[i] * X_[i]) % (unsigned long long)modulo_); + if (i < n_ - 1) X_[i] = X_[i+1]; + } + X_[n_ - 1] = res; + return res; +} + +// --------------------------- Reader's methods ------------------------------- + +Reader::Reader(const char* file) { + assert((input = fopen(file, "r")) != NULL); + head = tail= 0; + line = position = 1; +} + +Reader::Reader(FILE* _input) { + input = _input; + head = tail = 0; + line = position = 1; +} + +Reader::~Reader() { + assert(fclose(input) == 0); +} + +void Reader::fillBuffer() { + while ((tail + 1) % bufferSize != head) { + int v = getc(input); + if (v == EOF) break; + Buffer[tail] = (char)v; + tail = (tail + 1) % bufferSize; + } +} + +bool Reader::isEOF() { + fillBuffer(); + return head == tail; +} + +char Reader::read(bool move) { + fillBuffer(); + assert((head != tail) || (!move)); + if (head == tail) return 0; + char v = Buffer[head]; + if (move) { + if (isEOLN(v)) { + line++; + position = 1; + } else { + position++; + } + head = (head + 1) % bufferSize; + } + return v; +} + +// ---------------------------- Scanner's methods ----------------------------- + +Scanner::Scanner(const char* file, Lang _lang): lang(_lang) { + reader = new Reader(file); + end = endDefault; +} + +Scanner::Scanner(const char* file, void(*endf)(const char* msg, int line, int position), Lang _lang): lang(_lang) { + reader = new Reader(file); + end = endf; +} + +Scanner::Scanner(FILE* input, Lang _lang): lang(_lang) { + reader = new Reader(input); + end = endDefault; +} + +Scanner::Scanner(FILE* input, void(*endf)(const char* msg, int line, int position), Lang _lang): lang(_lang) { + reader = new Reader(input); + end = endf; +} + +Scanner::~Scanner() { + delete reader; +} + +void Scanner::error(const char* msg) { + int l = reader->getLine(); + int p = reader->getPosition(); + delete reader; + reader = NULL; + (*end)(msg, l, p); +} + +int Scanner::skipWhitespaces() { + int result = 0; + while (isWhitespace(reader->read())) { + reader->read(1); + result++; + } + return result; +} + + +int Scanner::skipWhitespacesUntilEOLN() { + int result = 0; + while (isWhitespace(reader->read()) && !isEOLN(reader->read())) { + reader->read(1); + result++; + } + return result; +} + + +// INTEGERS + +int Scanner::readInt(int min_value, int max_value) { + return (int)readLL(min_value, max_value); +} + +uint Scanner::readUInt(uint min_value, uint max_value) { + return (uint)readULL(min_value, max_value); +} + +inline bool lower_equal(ull a, bool sign_a, ull b, bool sign_b) { + if (sign_a != sign_b) return sign_a; + if (sign_a) return a >= b; + return a <= b; +} +inline ull spec_abs(ll x) { + if (x < 0) return (-(x + 1)) + 1; + return x; +} + +ll Scanner::readLL(ll min_value, ll max_value) { + assert(min_value <= max_value); + bool sign; + ull val; + readULL(max(spec_abs(min_value), spec_abs(max_value)), val, sign); + ll v = val; + if (!(lower_equal(spec_abs(min_value), min_value < 0, v, sign) && + lower_equal(v, sign, spec_abs(max_value), max_value < 0))) + error(msgOutOfRangeInt[lang]); + if (sign) v *= -1; + return v; +} + +ull Scanner::readULL(ull min_value, ull max_value) { + assert(min_value <= max_value); + bool sign; + ull val; + readULL(max_value, val, sign); + if (sign) error(msgOutOfRangeInt[lang]); + if (!(min_value <= val)) + error(msgOutOfRangeInt[lang]); + return val; +} + +// REAL NUMBERS + +float Scanner::readFloat(float min_value, float max_value) { + return (float)readLDouble(min_value, max_value); +} + +double Scanner::readDouble(double min_value, double max_value) { + return (double)readLDouble(min_value, max_value); +} + +long double Scanner::readLDouble(long double min_value, long double max_value) { + assert(min_value <= max_value); + bool sign; + ldb val; + readLDB(val, sign); + if (sign) val *= -1; + if (!(min_value <= val && val <= max_value)) + error(msgOutOfRangeReal[lang]); + return val; +} + +// STRINGS + +int Scanner::readString(char* s, int size) { + int x = 0; + while ( x < size - 1 && !isEOF() && !isWhitespace(reader->read())) + s[x++] = reader->read(1); + s[x]=0; + return x; +} + +int Scanner::readLine(char* s, int size) { + int x = 0; + while ( x < size - 1 && !isEOLN(reader->read()) && !isEOF()) + s[x++] = reader->read(1); + s[x] = 0; + if (isEOLN(reader->read())) reader->read(1); + return x; +} + +char Scanner::readChar() { + if (reader->isEOF()) error(msgNoChar[lang]); + return reader->read(1); +} + +// WHITESPACES + +void Scanner::readEof() { + if (!reader->isEOF()) error(msgNotEof[lang]); +} + +void Scanner::readEoln() { + if (!isEOLN(reader->read())) error(msgNotEoln[lang]); + reader->read(1); +} + +void Scanner::readEofOrEoln() { + if (isEOLN(reader->read())) { + reader->read(1); + } else if (!reader->isEOF()) { + error(msgNotEofOrEoln[lang]); + } +} + + +void Scanner::readSpace() { + if (!isSpace(reader->read())) error(msgNotSpace[lang]); + reader->read(1); +} + +void Scanner::readTab() { + if (!isTab(reader->read())) error(msgNotTab[lang]); + reader->read(1); +} + +bool Scanner::isEOF() { + return reader->isEOF(); +} + + +// PROTECTED + +void Scanner::readULL(ull limit, ull &val, bool &sign) { + sign = 0; + val = 0; + sign = isMinus(reader->read()); + if (sign) reader->read(1); + int zeros = 0; + int valDigits = 0; + while ('0' == reader->read()) { + zeros++; + valDigits++; + reader->read(1); + if (zeros > 1) error(msgLeadingZeros[lang]); + } + int limDigits = 0; + ull tmp = limit; + while (tmp) { + limDigits++; + tmp /= 10; + } + if (!limDigits) limDigits = 1; + while (isdigit(reader->read())) { + valDigits++; + if (valDigits > limDigits) error(msgOutOfRangeInt[lang]); + char x = reader->read(1); + if (valDigits == limDigits) { + if (limit / 10 < val) error(msgOutOfRangeInt[lang]); + if (limit / 10 == val && limit % 10 < (ull)(x - '0')) error(msgOutOfRangeInt[lang]); + } + val = val * 10 + x - '0'; + } + if (val > 0 && zeros) error(msgLeadingZeros[lang]); + if (sign && zeros) error(msgMinusZero[lang]); + if (!valDigits) error(msgNoNumber[lang]); +} + +void Scanner::readLDB(ldb &val, bool &sign) { + sign = 0; + val = 0; + sign = isMinus(reader->read()); + if (sign) reader->read(1); + int zeros = 0; + int valDigits = 0; + while ('0' == reader->read()) { + zeros++; + valDigits++; + reader->read(1); + if (zeros > 1) error(msgLeadingZeros[lang]); + } + if (zeros && isdigit(reader->read())) error(msgLeadingZeros[lang]); + while (isdigit(reader->read())) { + valDigits++; + char x = reader->read(1); + val = val * 10.0 + x - '0'; + } + if (!valDigits) error(msgNoNumber[lang]); + if (isDot(reader->read())) { + reader->read(1); + ldb dec = 1; + int dotDigits = 0; + while (isdigit(reader->read())) { + dotDigits++; + if (dotDigits > realNumbersLimit) error(msgRealNumberLimit[lang]); + char x = reader->read(1); + dec /= 10.0; + val += dec * (x - '0'); + } + if (!dotDigits) error(msgBadRealNumberFormat[lang]); + } +} + +} // namespace oi + +#endif // OI_LIB_OI_H_ diff --git a/src/sinol_make/commands/inwer/__init__.py b/src/sinol_make/commands/inwer/__init__.py index 50e3bef9..58bf3e61 100644 --- a/src/sinol_make/commands/inwer/__init__.py +++ b/src/sinol_make/commands/inwer/__init__.py @@ -84,7 +84,7 @@ def verify_and_print_table(self) -> Dict[str, TestResult]: :return: dictionary of TestResult objects """ results = {} - sorted_tests = sorted(self.tests, key=lambda x: x[0]) + sorted_tests = sorted(self.tests, key=lambda test: package_util.get_group(test, self.task_id)) executions: List[InwerExecution] = [] for test in sorted_tests: results[test] = TestResult(test, self.task_id) diff --git a/src/sinol_make/commands/run/__init__.py b/src/sinol_make/commands/run/__init__.py index 2f486337..5f305a5a 100644 --- a/src/sinol_make/commands/run/__init__.py +++ b/src/sinol_make/commands/run/__init__.py @@ -282,22 +282,6 @@ def get_group(self, test_path): return int("".join(filter(str.isdigit, package_util.extract_test_id(test_path, self.ID)))) - def get_executable_key(self, executable): - name = package_util.get_file_name(executable) - value = [0, 0] - if name[3] == 's': - value[0] = 1 - suffix = name.split(".")[0][4:] - elif name[3] == 'b': - value[0] = 2 - suffix = name.split(".")[0][4:] - else: - suffix = name.split(".")[0][3:] - if suffix != "": - value[1] = int(suffix) - return tuple(value) - - def get_solution_from_exe(self, executable): file = os.path.splitext(executable)[0] for ext in self.SOURCE_EXTENSIONS: @@ -305,24 +289,8 @@ def get_solution_from_exe(self, executable): return file + ext util.exit_with_error("Source file not found for executable %s" % executable) - - def get_solutions(self, args_solutions): - if args_solutions is None: - solutions = [solution for solution in os.listdir("prog/") - if self.SOLUTIONS_RE.match(solution)] - return sorted(solutions, key=self.get_executable_key) - else: - solutions = [] - for solution in args_solutions: - if not os.path.isfile(solution): - util.exit_with_error("Solution %s does not exist" % solution) - if self.SOLUTIONS_RE.match(os.path.basename(solution)) is not None: - solutions.append(os.path.basename(solution)) - return sorted(solutions, key=self.get_executable_key) - - def get_executables(self, args_solutions): - return [package_util.get_executable(solution) for solution in self.get_solutions(args_solutions)] + return [package_util.get_executable(solution) for solution in package_util.get_solutions(self.ID, args_solutions)] def get_possible_score(self, groups): @@ -670,7 +638,7 @@ def run_solutions(self, compiled_commands, names, solutions): for test in self.tests: all_results[name][self.get_group(test)][test] = ExecutionResult(Status.CE) print() - executions.sort(key = lambda x: (self.get_executable_key(x[1]), x[2])) + executions.sort(key = lambda x: (package_util.get_executable_key(x[1]), x[2])) program_groups_scores = collections.defaultdict(dict) print_data = PrintData(0) @@ -1030,7 +998,7 @@ def set_constants(self): def validate_arguments(self, args): - compilers = compiler.verify_compilers(args, self.get_solutions(None)) + compilers = compiler.verify_compilers(args, package_util.get_solutions(self.ID, None)) def use_oiejq(): timetool_path = None @@ -1236,7 +1204,7 @@ def run(self, args): self.check_are_any_tests_to_run() self.set_scores() self.failed_compilations = [] - solutions = self.get_solutions(self.args.solutions) + solutions = package_util.get_solutions(self.ID, self.args.solutions) util.change_stack_size_to_unlimited() for solution in solutions: diff --git a/src/sinol_make/helpers/package_util.py b/src/sinol_make/helpers/package_util.py index 7b66ef49..b07c0aea 100644 --- a/src/sinol_make/helpers/package_util.py +++ b/src/sinol_make/helpers/package_util.py @@ -53,6 +53,48 @@ def get_solutions_re(task_id: str) -> re.Pattern: return re.compile(r"^%s[bs]?[0-9]*\.(cpp|cc|java|py|pas)$" % task_id) +def get_solutions_re(task_id): + return re.compile(r"^%s[bs]?[0-9]*\.(cpp|cc|java|py|pas)$" % task_id) + + +def get_executable_key(executable): + name = get_file_name(executable) + value = [0, 0] + if name[3] == 's': + value[0] = 1 + suffix = name.split(".")[0][4:] + elif name[3] == 'b': + value[0] = 2 + suffix = name.split(".")[0][4:] + else: + suffix = name.split(".")[0][3:] + if suffix != "": + value[1] = int(suffix) + return tuple(value) + + +def get_files_matching(patterns: List[str], directory: str) -> List[str]: + """ + Returns list of files matching given patterns. + If pattern is absolute path, it is returned as is. + If pattern is relative path, it is searched in current directory and in directory specified as argument. + :param patterns: List of patterns to match. + :param directory: Directory to search in. + :return: List of files matching given patterns. + """ + files_matching = set() + for solution in patterns: + if os.path.isabs(solution): + files_matching.add(solution) + else: + # If solution already has `/` prefix: + files_matching.update(glob.glob(os.path.join(os.getcwd(), solution))) + # If solution does not have `/` prefix: + files_matching.update(glob.glob(os.path.join(os.getcwd(), directory, solution))) + + return list(files_matching) + + def get_tests(task_id: str, arg_tests: Union[List[str], None] = None) -> List[str]: """ Returns list of tests to run. @@ -65,11 +107,36 @@ def get_tests(task_id: str, arg_tests: Union[List[str], None] = None) -> List[st if test[-3:] == ".in"] return sorted(all_tests, key=lambda test: get_test_key(test, task_id)) else: - existing_tests = set() - for test in arg_tests: - if os.path.exists(test): - existing_tests.add(test) - return sorted(list(existing_tests), key=lambda test: get_test_key(test, task_id)) + existing_tests = [] + for test in get_files_matching(arg_tests, "in"): + if not os.path.isfile(test): + util.exit_with_error("Test %s does not exist" % test) + if os.path.splitext(test)[1] == ".in": + existing_tests.append(os.path.join("in", os.path.basename(test))) + return sorted(existing_tests, key=lambda test: get_test_key(test, task_id)) + + +def get_solutions(task_id: str, args_solutions: Union[List[str], None] = None) -> List[str]: + """ + Returns list of solutions to run. + :param task_id: Task id. + :param args_solutions: Solutions specified in command line arguments. If None, all solutions are returned. + :return: List of solutions to run. + """ + solutions_re = get_solutions_re(task_id) + if args_solutions is None: + solutions = [solution for solution in os.listdir("prog/") + if solutions_re.match(solution)] + return sorted(solutions, key=get_executable_key) + else: + solutions = [] + for solution in get_files_matching(args_solutions, "prog"): + if not os.path.isfile(solution): + util.exit_with_error("Solution %s does not exist" % solution) + if solutions_re.match(os.path.basename(solution)) is not None: + solutions.append(os.path.basename(solution)) + + return sorted(solutions, key=get_executable_key) def get_file_name(file_path): diff --git a/tests/commands/run/test_integration.py b/tests/commands/run/test_integration.py index 3941feef..dbda4fb8 100644 --- a/tests/commands/run/test_integration.py +++ b/tests/commands/run/test_integration.py @@ -149,7 +149,7 @@ def test_flag_tests(create_package, time_tool): except SystemExit: pass - assert command.tests == [test] + assert command.tests == [os.path.join("in", os.path.basename(test))] @pytest.mark.parametrize("create_package", [get_checker_package_path()], indirect=True) @@ -235,6 +235,33 @@ def test_flag_solutions(capsys, create_package, time_tool): assert os.path.basename(solutions[1]) not in out +@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path(), + get_checker_package_path()], indirect=True) +def test_flag_solutions_multiple(capsys, create_package, time_tool): + """ + Test flag --solutions with multiple solutions. + """ + package_path = create_package + create_ins_outs(package_path) + + task_id = package_util.get_task_id() + solutions = [ + os.path.basename(file) + for file in package_util.get_files_matching_pattern(task_id, f'{task_id}?.*') + ] + parser = configure_parsers() + args = parser.parse_args(["run", "--solutions", solutions[0], os.path.join("prog", solutions[1]), + "--time-tool", time_tool]) + command = Command() + command.run(args) + + out = capsys.readouterr().out + + assert os.path.basename(solutions[0]) in out + assert os.path.basename(solutions[1]) in out + assert os.path.basename(solutions[2]) not in out + + @pytest.mark.parametrize("create_package", [get_weak_compilation_flags_package_path()], indirect=True) def test_weak_compilation_flags(create_package): """ diff --git a/tests/commands/run/test_unit.py b/tests/commands/run/test_unit.py index 354f2a98..bb9502aa 100644 --- a/tests/commands/run/test_unit.py +++ b/tests/commands/run/test_unit.py @@ -14,27 +14,10 @@ def test_get_output_file(): assert command.get_output_file("in/abc1a.in") == "out/abc1a.out" -def test_get_solutions(): - os.chdir(get_simple_package_path()) - command = get_command() - - solutions = command.get_solutions(None) - assert solutions == ["abc.cpp", "abc1.cpp", "abc2.cpp", "abc3.cpp", "abc4.cpp"] - solutions = command.get_solutions(["prog/abc.cpp"]) - assert solutions == ["abc.cpp"] - assert "abc1.cpp" not in solutions - - -def test_get_executable_key(): - os.chdir(get_simple_package_path()) - command = get_command() - assert command.get_executable_key("abc1.cpp.e") == (0, 1) - - def test_compile_solutions(create_package): package_path = create_package command = get_command(package_path) - solutions = command.get_solutions(None) + solutions = package_util.get_solutions("abc", None) result = command.compile_solutions(solutions) assert result == [True for _ in solutions] diff --git a/tests/helpers/test_package_util.py b/tests/helpers/test_package_util.py index de5f294a..44ca3ce4 100644 --- a/tests/helpers/test_package_util.py +++ b/tests/helpers/test_package_util.py @@ -36,6 +36,30 @@ def test_get_tests(create_package): tests = package_util.get_tests("abc", None) assert tests == ["in/abc1a.in", "in/abc2a.in", "in/abc3a.in", "in/abc4a.in"] + with tempfile.TemporaryDirectory() as tmpdir: + def create_file(name): + with open(os.path.join(tmpdir, "in", name), "w") as f: + f.write("") + + os.chdir(tmpdir) + os.mkdir("in") + create_file("abc0.in") + create_file("abc0a.in") + create_file("abc1ocen.in") + create_file("abc2ocen.in") + create_file("abc1a.in") + create_file("abc1b.in") + create_file("abc2a.in") + + assert set(package_util.get_tests("abc", None)) == \ + {"in/abc0.in", "in/abc0a.in", "in/abc1a.in", "in/abc1b.in", "in/abc1ocen.in", "in/abc2a.in", "in/abc2ocen.in"} + assert package_util.get_tests("abc", ["in/abc1a.in"]) == ["in/abc1a.in"] + assert package_util.get_tests("abc", ["in/abc??.in"]) == \ + ["in/abc0a.in", "in/abc1a.in", "in/abc1b.in", "in/abc2a.in"] + assert package_util.get_tests("abc", ["abc1a.in"]) == ["in/abc1a.in"] + assert package_util.get_tests("abc", ["abc?ocen.in", "abc0.in"]) == ["in/abc0.in", "in/abc1ocen.in", "in/abc2ocen.in"] + assert package_util.get_tests("abc", [os.path.join(tmpdir, "in", "abc1a.in")]) == ["in/abc1a.in"] + def test_extract_file_name(): assert package_util.get_file_name("in/abc1a.in") == "abc1a.in" @@ -203,3 +227,44 @@ def test_validate_files(create_package, capsys): package_util.validate_test_names(task_id) out = capsys.readouterr().out assert "def1a.out" in out + + +def test_get_executable_key(): + os.chdir(get_simple_package_path()) + assert package_util.get_executable_key("abc1.cpp.e") == (0, 1) + + +def test_get_solutions(): + os.chdir(get_simple_package_path()) + + solutions = package_util.get_solutions("abc", None) + assert solutions == ["abc.cpp", "abc1.cpp", "abc2.cpp", "abc3.cpp", "abc4.cpp"] + solutions = package_util.get_solutions("abc", ["prog/abc.cpp"]) + assert solutions == ["abc.cpp"] + assert "abc1.cpp" not in solutions + + with tempfile.TemporaryDirectory() as tmpdir: + def create_file(name): + with open(os.path.join(tmpdir, "prog", name), "w") as f: + f.write("") + + os.chdir(tmpdir) + os.mkdir("prog") + + create_file("abc.cpp") + create_file("abc1.cpp") + create_file("abc2.cpp") + create_file("abcs1.cpp") + create_file("abcs2.cpp") + + assert package_util.get_solutions("abc", None) == ["abc.cpp", "abc1.cpp", "abc2.cpp", "abcs1.cpp", "abcs2.cpp"] + assert package_util.get_solutions("abc", ["prog/abc.cpp"]) == ["abc.cpp"] + assert package_util.get_solutions("abc", ["abc.cpp"]) == ["abc.cpp"] + assert package_util.get_solutions("abc", [os.path.join(tmpdir, "prog", "abc.cpp")]) == ["abc.cpp"] + assert package_util.get_solutions("abc", ["prog/abc?.cpp"]) == ["abc1.cpp", "abc2.cpp"] + assert package_util.get_solutions("abc", ["abc?.cpp"]) == ["abc1.cpp", "abc2.cpp"] + assert package_util.get_solutions("abc", ["prog/abc*.cpp"]) == ["abc.cpp", "abc1.cpp", "abc2.cpp", "abcs1.cpp", "abcs2.cpp"] + assert package_util.get_solutions("abc", ["abc*.cpp"]) == ["abc.cpp", "abc1.cpp", "abc2.cpp", "abcs1.cpp", "abcs2.cpp"] + assert package_util.get_solutions("abc", ["prog/abc.cpp", "abc1.cpp"]) == ["abc.cpp", "abc1.cpp"] + assert package_util.get_solutions("abc", ["prog/abc.cpp", "abc?.cpp"]) == ["abc.cpp", "abc1.cpp", "abc2.cpp"] + assert package_util.get_solutions("abc", ["abc.cpp", "abc2.cpp", "abcs2.cpp"]) == ["abc.cpp", "abc2.cpp", "abcs2.cpp"]