diff --git a/example_package/config.yml b/example_package/config.yml index 17f9f482..c92d203d 100644 --- a/example_package/config.yml +++ b/example_package/config.yml @@ -87,6 +87,10 @@ sinol_latex_compiler: auto # This key is optional and should be a list of tests. sinol_static_tests: ["__ID__0.in", "__ID__0a.in"] +# Total score of the task is defined in `sinol_total_score` key. +# If this key is not specified, then this defaults to 100. +sinol_total_score: 100 + # sinol-make can check if the solutions run as expected when using `run` command. # Key `sinol_expected_scores` defines expected scores for each solution on each tests. # There should be no reason to change this key manually. diff --git a/src/sinol_make/commands/run/__init__.py b/src/sinol_make/commands/run/__init__.py index 53f38bec..a10ed31a 100644 --- a/src/sinol_make/commands/run/__init__.py +++ b/src/sinol_make/commands/run/__init__.py @@ -827,8 +827,10 @@ def set_scores(self): self.scores[group] = self.config["scores"][group] total_score += self.scores[group] - if total_score != 100: - print(util.warning("WARN: Scores sum up to %d instead of 100." % total_score)) + total_score_config = 100 if 'sinol_total_score' not in self.config.keys() else self.config['sinol_total_score'] + + if total_score != total_score_config: + print(util.warning("WARN: Scores sum up to %d instead of %d." % (total_score, total_score_config))) print() self.possible_score = self.contest.get_possible_score(self.groups, self.scores) diff --git a/src/sinol_make/contest_types/oij.py b/src/sinol_make/contest_types/oij.py index 4341c79e..a9ffaa7b 100644 --- a/src/sinol_make/contest_types/oij.py +++ b/src/sinol_make/contest_types/oij.py @@ -22,14 +22,16 @@ def argument_overrides(self, args: argparse.Namespace) -> argparse.Namespace: def verify_pre_gen(self): """ - Verify if scores sum up to 100. + Verify if scores sum up correctly. """ config = package_util.get_config() if 'scores' not in config: util.exit_with_error("Scores are not defined in config.yml.") total_score = sum(config['scores'].values()) - if total_score != 100: - util.exit_with_error(f"Total score in config is {total_score}, but should be 100.") + total_score_config = 100 if 'sinol_total_score' not in config else config['sinol_total_score'] + + if total_score != total_score_config: + util.exit_with_error(f"Total score in config is {total_score}, but should be {total_score_config}.") def verify_tests_order(self): return True diff --git a/tests/commands/verify/test_integration.py b/tests/commands/verify/test_integration.py index 8402c6af..f9f6e3f6 100644 --- a/tests/commands/verify/test_integration.py +++ b/tests/commands/verify/test_integration.py @@ -174,3 +174,37 @@ def test_no_gen_parameters(capsys, create_package): run(["--no-ingen"]) assert os.path.exists(os.path.join(create_package, "in", "abc2a.in")) assert os.path.exists(os.path.join(create_package, "out", "abc2a.out")) + +@pytest.mark.parametrize("create_package", [util.get_score_package()], indirect=True) +def test_total_score_in_config(capsys, create_package): + """ + Test if total score overwrites default 100 for verification if contest type is OIJ. + """ + run() + + if os.path.exists(paths.get_cache_path()): + shutil.rmtree(paths.get_cache_path()) + cache.create_cache_dirs() + config = package_util.get_config() + config["sinol_total_score"] = 25 + sm_util.save_config(config) + with pytest.raises(SystemExit) as e: + run() + assert e.value.code == 1 + out = capsys.readouterr().out + assert "Total score in config is 40, but should be 25." in out + + +@pytest.mark.parametrize("create_package", [util.get_score_package()], indirect=True) +def test_total_score_in_config_oi(capsys, create_package): + """ + Test if total_score does not overwrite default 100 for verification if contest type is OI. + """ + config = package_util.get_config() + config["sinol_contest_type"] = "oi" + sm_util.save_config(config) + with pytest.raises(SystemExit) as e: + run() + assert e.value.code == 1 + out = capsys.readouterr().out + assert "Total score in config is 40, but should be 100." in out diff --git a/tests/packages/score/config.yml b/tests/packages/score/config.yml new file mode 100644 index 00000000..51b5e0a0 --- /dev/null +++ b/tests/packages/score/config.yml @@ -0,0 +1,19 @@ +title: Package for testing sinol_total_score configuration +sinol_task_id: score +sinol_contest_type: oij +memory_limit: 16000 +time_limit: 1000 +sinol_total_score: 40 +scores: + 1: 10 + 2: 10 + 3: 10 + 4: 10 +sinol_expected_scores: + score.cpp: + expected: + 1: {points: 10, status: OK} + 2: {points: 10, status: OK} + 3: {points: 10, status: OK} + 4: {points: 10, status: OK} + points: 40 diff --git a/tests/packages/score/doc/.gitkeep b/tests/packages/score/doc/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/packages/score/in/.gitkeep b/tests/packages/score/in/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/packages/score/out/.gitkeep b/tests/packages/score/out/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/packages/score/prog/oi.h b/tests/packages/score/prog/oi.h new file mode 100644 index 00000000..01ca7094 --- /dev/null +++ b/tests/packages/score/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/tests/packages/score/prog/score.cpp b/tests/packages/score/prog/score.cpp new file mode 100644 index 00000000..a40c98d0 --- /dev/null +++ b/tests/packages/score/prog/score.cpp @@ -0,0 +1,9 @@ +#include + +using namespace std; + +int main() { + int a, b; + cin >> a >> b; + cout << a + b << "\n"; +} diff --git a/tests/packages/score/prog/scoreingen.cpp b/tests/packages/score/prog/scoreingen.cpp new file mode 100644 index 00000000..b8776f31 --- /dev/null +++ b/tests/packages/score/prog/scoreingen.cpp @@ -0,0 +1,19 @@ +#include +#include "oi.h" + +using namespace std; + +int main() { + ofstream f("score1a.in"); + f << "1 3\n"; + f.close(); + f.open("score2a.in"); + f << "2 5\n"; + f.close(); + f.open("score3a.in"); + f << "3 7\n"; + f.close(); + f.open("score4a.in"); + f << "4 9\n"; + f.close(); +} diff --git a/tests/util.py b/tests/util.py index be399e31..49e37fad 100644 --- a/tests/util.py +++ b/tests/util.py @@ -198,6 +198,11 @@ def get_two_interactive_package(): """ return os.path.join(os.path.dirname(__file__), "packages", "two_interactive") +def get_score_package(): + """ + Get path to package with custom sinol_total_score (/tests/packages/score) + """ + return os.path.join(os.path.dirname(__file__), "packages", "score") def create_ins(package_path, task_id): """