vfleaking / uoj

Universal Online Judge
MIT License
527 stars 107 forks source link

升级 testlib 版本 #108

Open renbaoshuo opened 1 year ago

renbaoshuo commented 1 year ago

当前 UOJ 使用的 testlib 版本为 0.9.5,而 MikeMirzayanov/testlib 中的版本已经到达了 0.9.40,建议升级一下以适配基于新版 testlib 编写的 checker / validator / ...。

yhx-12243 commented 1 year ago

确实,不然 testlib 不能用 readInts 这种东西还挺难受的

vfleaking commented 1 year ago

很赞成,而且老版 testlib 有个 BUG(一下子找不到对应的链接了)。目前 UOJ 管理员传题,也多是使用自己上传的新版 testlib。

主要难点如下:

  1. uoj 用的 testlib 是个魔改版,我记得主要魔改的地方是 registerInteraction 的部分。可能需要 diff 一下还有没有别的地方被魔改了
  2. 以前上传的题目在 testlib 更新后,最好重新生成一遍数据包,并检查题目是否还能正常评测。感觉有点工程量。。(这也是 UOJ 的 testlib 一直都没有更新的主要原因)
yhx-12243 commented 1 year ago

感觉一种可能的方法是像 Testlib-for-Lemons 一样开个类似的 “Testlib-for-uoj” 库,然后和上游 MikeMirzayanov/testlib 保持一种同步,不过代价是可能要稍微改一下 include 结构,因为 git 的 submodule 只能加目录

renbaoshuo commented 1 year ago

以下是 UOJ 目前使用的 testlib.h 与 testlib 0.9.5 版之间的 diff:

--- testlib-0.9.5/testlib.h
+++ judger/uoj_judger/include/testlib.h
@@ -120,12 +120,20 @@ const char* latestFeatures[] = {
 #endif

 /* Overrides random() for Borland C++. */
+#ifdef __EMSCRIPTEN__
+#define srand __srand_deprecated
+#define rand __rand_deprecated
+#endif
 #define random __random_deprecated
 #include <stdlib.h>
 #include <cstdlib>
 #include <climits>
 #include <algorithm>
 #undef random
+#ifdef __EMSCRIPTEN__
+#undef rand
+#undef srand
+#endif

 #include <cstdio>
 #include <cctype>
@@ -140,6 +148,10 @@ const char* latestFeatures[] = {

 #include <fcntl.h>

+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+#endif
+
 #if ( _WIN32 || __WIN32__ || _WIN64 || __WIN64__ )
 #   if !defined(_MSC_VER) || _MSC_VER>1400
 #       include <windows.h>
@@ -152,6 +164,10 @@ const char* latestFeatures[] = {
 #   define WORD unsigned short
 #endif

+#ifdef linux
+#include <unistd.h>
+#endif
+
 #ifndef LLONG_MIN
 #define LLONG_MIN   (-9223372036854775807LL - 1)
 #endif
@@ -235,8 +251,8 @@ const char* latestFeatures[] = {
 #else
 #   define NORETURN
 #endif
-                   
-static char __testlib_format_buffer[16777216];
+
+static char __testlib_format_buffer[1024];
 static int __testlib_format_buffer_usage_count = 0;

 #define FMT_TO_RESULT(fmt, cstr, result)  std::string result;                              \
@@ -314,21 +330,17 @@ static bool __testlib_isNaN(double r)
     std::memcpy((void*)&llr1, (void*)&ra, sizeof(double)); 
     ra = -ra;
     std::memcpy((void*)&llr2, (void*)&ra, sizeof(double)); 
-    long long llnan = 0xFFF8000000000000;
+    long long llnan = 0xFFF8000000000000ll;
     return __testlib_prelimIsNaN(r) || llnan == llr1 || llnan == llr2;
 }

 static double __testlib_nan()
 {
     __TESTLIB_STATIC_ASSERT(sizeof(double) == sizeof(long long));
-#ifndef NAN
-    long long llnan = 0xFFF8000000000000;
+    long long llnan = 0xFFF8000000000000ll;
     double nan;
     std::memcpy(&nan, &llnan, sizeof(double));
     return nan;
-#else
-    return NAN;
-#endif
 }

 static bool __testlib_isInfinite(double r)
@@ -351,6 +363,23 @@ static void __testlib_set_binary(std::FI
 #endif
 }

+static FILE *__testlib_fopen(const char *name, const char *mode)
+{
+#ifdef __EMSCRIPTEN__
+   EM_ASM(
+       try {
+           FS.stat('/cwd');
+       } catch (e) {
+           FS.mkdir('/cwd');
+           FS.mount(NODEFS, { root: '.' }, '/cwd');
+       }
+   );
+   return fopen((std::string("/cwd/") + name).c_str(), mode);
+#else
+   return fopen(name, mode);
+#endif
+}
+
 /*
  * Very simple regex-like pattern.
  * It used for two purposes: validation and generation.
@@ -1629,8 +1658,8 @@ struct InStream
     /* As "readInteger()" but ensures that value in the range [minv,maxv]. */
     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 new double. Ignores white-spaces into the non-strict mode 
      * (strict mode is used in validators usually). 
@@ -1814,6 +1843,8 @@ InStream::InStream()

 InStream::InStream(const InStream& baseStream, std::string content)
 {
+   file = (FILE*)0xbadfeed;
+   stdfile = false;
     reader = new StringInputStreamReader(content);
     opened = true;
     strict = baseStream.strict;
@@ -1864,6 +1895,34 @@ void InStream::textColor(WORD color)
     HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
     SetConsoleTextAttribute(handle, color);
 #endif
+
+#ifdef linux
+   char *shell_path = getenv("SHELL");
+   if (shell_path && strcmp(shell_path, "/bin/bash") == 0 && isatty(2))
+   {
+       switch (color)
+       {
+           case 0:
+               fprintf(stderr, "\e[0m");
+               break;
+           case LightRed:
+               fprintf(stderr, "\e[0;31m");
+               break;
+           case LightGreen:
+               fprintf(stderr, "\e[0;32m");
+               break;
+           case LightYellow:
+               fprintf(stderr, "\e[0;33m");
+               break;
+           case LightCyan:
+               fprintf(stderr, "\e[0;36m");
+               break;
+           case LightGray:
+               fprintf(stderr, "\e[0m");
+               break;
+       }
+   }
+#endif
 }

 NORETURN void halt(int exitCode)
@@ -1874,6 +1933,9 @@ NORETURN void halt(int exitCode)
     std::fprintf(stderr, "Exit code: %d\n", exitCode);
     InStream::textColor(InStream::LightGray);
 #endif
+#ifdef linux
+   InStream::textColor(0);
+#endif
     std::exit(exitCode);
 }

@@ -1945,7 +2007,7 @@ NORETURN void InStream::quit(TResult res

     if (resultName != "")
     {
-        resultFile = std::fopen(resultName.c_str(), "w");
+        resultFile = __testlib_fopen(resultName.c_str(), "w");
         if (resultFile == NULL)
             quit(_fail, "Can not write to the result file");
         if (appesMode)
@@ -2066,7 +2128,7 @@ void InStream::reset()
         close();

     if (!stdfile)
-        if (NULL == (file = std::fopen(name.c_str(), "rb")))
+        if (NULL == (file = __testlib_fopen(name.c_str(), "rb")))
         {
             if (mode == _output)
                 quits(_pe, std::string("File not found: \"") + name + "\"");
@@ -2085,8 +2147,17 @@ void InStream::reset()
 void InStream::init(std::string fileName, TMode mode)
 {
     opened = false;
-    name = fileName;
-    stdfile = false;
+   if (fileName == "/dev/stdin")
+   {
+       name = "stdin";
+       this->file = stdin;
+       stdfile = true;
+   }
+   else
+   {
+       name = fileName;
+       stdfile = false;
+   }
     this->mode = mode;
     reset();
 }
@@ -2524,24 +2595,24 @@ 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 (variableName.empty())
-            quit(_wa, ("Integer " + 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 [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str());
-    }
+   {
+       if (variableName.empty())
+           quit(_wa, ("Integer " + 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 [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str());
+   }

     return result;
 }

 int InStream::readInteger(int minv, int maxv, const std::string& variableName)
 {
-    return readInt(minv, maxv, variableName);
+    return readInt(minv, maxv, variableName.c_str());
 }

 double InStream::readReal()
@@ -2659,7 +2730,7 @@ bool InStream::eoln()
     {
         bool returnCr = false;

-#ifdef ON_WINDOWS
+#ifdef CR_MUST_IN_EOL
         if (c != CR)
         {
             reader->unreadChar(c);
@@ -2955,18 +3026,6 @@ void registerGen(int argc, char* argv[])
     registerGen(argc, argv, 0);
 }
 #else
-#ifdef __GNUC__
-    __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.")))
-#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."))
-#endif
 void registerGen(int argc, char* argv[])
 {
     std::fprintf(stderr, "Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)."
@@ -3022,9 +3081,12 @@ void registerInteraction(int argc, char*

     inf.init(argv[1], _input);

-    tout.open(argv[2], std::ios_base::out);
-    if (tout.fail() || !tout.is_open())
-        quit(_fail, std::string("Can not write to the test-output-file '") + argv[2] + std::string("'"));
+   if (strcmp(argv[2], "stdout") != 0)
+   {
+       tout.open(argv[2], std::ios_base::out);
+       if (tout.fail() || !tout.is_open())
+           quit(_fail, std::string("Can not write to the test-output-file '") + argv[2] + std::string("'"));
+   }

     ouf.init(stdin, _output);

@@ -3228,9 +3290,6 @@ void shuffle(_RandomAccessIter __first,

 template<typename _RandomAccessIter>
-#ifdef __GNUC__
-__attribute__ ((error("Don't use random_shuffle(), use shuffle() instead")))
-#endif
 void random_shuffle(_RandomAccessIter , _RandomAccessIter )
 {
     quitf(_fail, "Don't use random_shuffle(), use shuffle() instead");
@@ -3242,9 +3301,6 @@ void random_shuffle(_RandomAccessIter ,
 #  define RAND_THROW_STATEMENT
 #endif

-#ifdef __GNUC__
-__attribute__ ((error("Don't use rand(), use rnd.next() instead")))
-#endif
 int rand() RAND_THROW_STATEMENT
 {
     quitf(_fail, "Don't use rand(), use rnd.next() instead");
@@ -3253,12 +3309,6 @@ int rand() RAND_THROW_STATEMENT
     //throw "Don't use rand(), use rnd.next() instead";
 }

-#ifdef __GNUC__
-__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
 void srand(unsigned int seed) RAND_THROW_STATEMENT
 {
     quitf(_fail, "Don't use srand(), you should use "