diff --git a/pkg/language/internal/testutils/test_args.go b/pkg/internal/testutils/test_args.go similarity index 100% rename from pkg/language/internal/testutils/test_args.go rename to pkg/internal/testutils/test_args.go diff --git a/pkg/language/isolate_integration_test.go b/pkg/language/isolate_integration_test.go index 0874b0e8..08da0368 100644 --- a/pkg/language/isolate_integration_test.go +++ b/pkg/language/isolate_integration_test.go @@ -1,9 +1,8 @@ package language_test import ( + "github.com/mraron/njudge/pkg/internal/testutils" "github.com/mraron/njudge/pkg/language" - "github.com/mraron/njudge/pkg/language/internal/testutils" - "github.com/mraron/njudge/pkg/language/langs/cpp" _ "github.com/mraron/njudge/pkg/language/langs/csharp" _ "github.com/mraron/njudge/pkg/language/langs/cython3" diff --git a/pkg/language/langs/cpp/cpp_test.go b/pkg/language/langs/cpp/cpp_test.go index 20708310..52392653 100644 --- a/pkg/language/langs/cpp/cpp_test.go +++ b/pkg/language/langs/cpp/cpp_test.go @@ -3,7 +3,7 @@ package cpp_test import ( "bytes" "context" - "github.com/mraron/njudge/pkg/language/internal/testutils" + "github.com/mraron/njudge/pkg/internal/testutils" "github.com/mraron/njudge/pkg/language/langs/cpp" "github.com/mraron/njudge/pkg/language/memory" "github.com/mraron/njudge/pkg/language/sandbox" diff --git a/pkg/language/langs/zip/zip.go b/pkg/language/langs/zip/zip.go index 82c464d4..fd6d2bea 100644 --- a/pkg/language/langs/zip/zip.go +++ b/pkg/language/langs/zip/zip.go @@ -10,32 +10,28 @@ import ( "github.com/mraron/njudge/pkg/language" ) -type zip struct{} +type Zip struct{} -func (zip) ID() string { +func (Zip) ID() string { return "zip" } -func (zip) DisplayName() string { +func (Zip) DisplayName() string { return "ZIP archívum" } -func (zip) DefaultFilename() string { +func (Zip) DefaultFilename() string { return "main.zip" } -func (zip) Compile(ctx context.Context, s sandbox.Sandbox, f sandbox.File, stderr io.Writer, extras []sandbox.File) (*sandbox.File, error) { - return nil, nil +func (Zip) Compile(_ context.Context, _ sandbox.Sandbox, f sandbox.File, _ io.Writer, _ []sandbox.File) (*sandbox.File, error) { + return &f, nil } -func (zip) Run(ctx context.Context, s sandbox.Sandbox, binary sandbox.File, stdin io.Reader, stdout io.Writer, tl time.Duration, ml memory.Amount) (*sandbox.Status, error) { +func (Zip) Run(_ context.Context, _ sandbox.Sandbox, _ sandbox.File, _ io.Reader, _ io.Writer, _ time.Duration, _ memory.Amount) (*sandbox.Status, error) { return &sandbox.Status{}, nil } -func (zip) Test(sandbox.Sandbox) error { - return nil -} - func init() { - language.DefaultStore.Register("zip", zip{}) + language.DefaultStore.Register("zip", Zip{}) } diff --git a/pkg/language/sandbox/dummy.go b/pkg/language/sandbox/dummy.go index 6c3ba0a3..5f657a55 100644 --- a/pkg/language/sandbox/dummy.go +++ b/pkg/language/sandbox/dummy.go @@ -34,6 +34,13 @@ type Dummy struct { type DummyOption func(*Dummy) error +func DummyWithLogger(logger *slog.Logger) DummyOption { + return func(dummy *Dummy) error { + dummy.Logger = logger + return nil + } +} + func NewDummy(opts ...DummyOption) (*Dummy, error) { dummyID += 1 res := &Dummy{ @@ -55,7 +62,7 @@ func (d *Dummy) Id() string { return strconv.Itoa(d.ID) } -func (d *Dummy) Init(ctx context.Context) error { +func (d *Dummy) Init(_ context.Context) error { var err error if d.Dir, err = os.MkdirTemp("", DummyPattern); err != nil { return err @@ -115,189 +122,17 @@ func (d *Dummy) Run(ctx context.Context, config RunConfig, command string, comma st.Verdict = VerdictTL } else if strings.HasPrefix(err.Error(), "exit status") || strings.HasPrefix(err.Error(), "signal:") { // TODO st.Verdict = VerdictRE + st.ExitCode = err.(*exec.ExitError).ExitCode() } - st.ExitCode = err.(*exec.ExitError).ExitCode() } return &st, nil } -func (d *Dummy) Cleanup(ctx context.Context) error { +func (d *Dummy) Cleanup(_ context.Context) error { d.Logger.Info("cleanup dummy sandbox", "dir", d.Dir) d.inited = false d.OsFS = OsFS{} return os.RemoveAll(d.Dir) } - -/* -func (s Dummy) Id() string { - return s.tmpdir -} - -func (s *Dummy) Init(logger *log.Logger) error { - var err error - if s.tmpdir, err = os.MkdirTemp("", "dummysandbox"); err != nil { - return err - } - - s.workingDir = s.tmpdir - s.logger = logger - return nil -} - -func (s Dummy) Pwd() string { - return s.tmpdir -} - -func (s *Dummy) CreateFilePopulated(name string, r io.Reader) error { - filename := filepath.Join(s.tmpdir, name) - s.logger.Print("Creating file ", filename) - - f, err := os.Create(filename) - if err != nil { - s.logger.Print("Error occurred while creating file ", err) - return err - } - - if _, err := io.Copy(f, r); err != nil { - s.logger.Print("Error occurred while populating it with its content: ", err) - f.Close() - return err - } - - return f.Close() -} - -func (s *Dummy) Create(name string) (io.WriteCloser, error) { - return os.Create(filepath.Join(s.Pwd(), name)) -} - -func (s Dummy) Open(name string) (fs.File, error) { - return os.Open(filepath.Join(s.Pwd(), name)) -} - -func (s Dummy) MakeExecutable(name string) error { - filename := filepath.Join(s.Pwd(), name) - - err := os.Chmod(filename, 0777) - s.logger.Print("Making executable: ", filename, " error: ", err) - - return err -} - -func (s *Dummy) SetMaxProcesses(i int) Sandbox { - return s -} - -func (s *Dummy) Env() Sandbox { - s.env = os.Environ() - return s -} - -func (s *Dummy) SetEnv(env string) Sandbox { - s.env = append(s.env, env+"="+os.Getenv(env)) - return s -} - -func (s *Dummy) AddArg(string) Sandbox { - return s -} - -func (s *Dummy) TimeLimit(tl time.Duration) Sandbox { - s.tl = tl - return s -} - -func (s *Dummy) MemoryLimit(int) Sandbox { - return s -} - -func (s *Dummy) Stdin(reader io.Reader) Sandbox { - s.stdin = reader - return s -} - -func (s *Dummy) Stderr(writer io.Writer) Sandbox { - s.stderr = writer - return s -} - -func (s *Dummy) Stdout(writer io.Writer) Sandbox { - s.stdout = writer - return s -} - -func (s *Dummy) MapDir(x string, y string, i []string, b bool) Sandbox { - return s -} - -func (s *Dummy) WorkingDirectory(dir string) Sandbox { - s.workingDir = dir - return s -} - -func (s *Dummy) Verbose() Sandbox { - return s -} - -func (s *Dummy) Run(prg string, needStatus bool) (Status, error) { - cmd := exec.Command("bash", "-c", prg) - cmd.Stdin = s.stdin - cmd.Stdout = s.stdout - cmd.Stderr = s.stderr - cmd.Dir = s.workingDir - cmd.Env = append(s.env, "PATH="+os.Getenv("PATH")+":"+s.tmpdir) - - var ( - st Status - errKill, errWait error - finish = make(chan bool, 1) - wg sync.WaitGroup - ) - - st.Verdict = VerdictOK - - start := time.NewTimer(s.tl) - if err := cmd.Start(); err != nil { - st.Verdict = VerdictXX - return st, err - } - defer start.Stop() - - wg.Add(1) - go func() { - defer wg.Done() - errWait = cmd.Wait() - finish <- true - }() - - select { - case <-start.C: - st.Verdict = VerdictTL - if errKill = cmd.Process.Kill(); errKill != nil { - st.Verdict = VerdictXX - } - case <-finish: - } - - wg.Wait() - - if errWait != nil && (strings.HasPrefix(errWait.Error(), "exit status") || strings.HasPrefix(errWait.Error(), "signal:")) { - if st.Verdict == VerdictOK { - st.Verdict = VerdictRE - } - errWait = nil - } - - if errWait != nil { - return st, errWait - } - - return st, errKill -} - -func (s *Dummy) Cleanup() error { - return os.RemoveAll(s.tmpdir) -} -*/ diff --git a/pkg/language/sandbox/isolate_test.go b/pkg/language/sandbox/isolate_test.go index 904ab3ea..dee61a0b 100644 --- a/pkg/language/sandbox/isolate_test.go +++ b/pkg/language/sandbox/isolate_test.go @@ -3,7 +3,7 @@ package sandbox_test import ( "context" "flag" - "github.com/mraron/njudge/pkg/language/internal/testutils" + "github.com/mraron/njudge/pkg/internal/testutils" "github.com/mraron/njudge/pkg/language/sandbox" "github.com/stretchr/testify/assert" "log/slog" diff --git a/pkg/problems/evaluation/run.go b/pkg/problems/evaluation/run.go index 5417ed4f..36f7600a 100644 --- a/pkg/problems/evaluation/run.go +++ b/pkg/problems/evaluation/run.go @@ -1,18 +1,35 @@ package evaluation import ( + "archive/zip" "bytes" "context" "errors" + "fmt" "github.com/mraron/njudge/pkg/language" + "github.com/mraron/njudge/pkg/language/memory" "github.com/mraron/njudge/pkg/language/sandbox" "github.com/mraron/njudge/pkg/problems" "github.com/spf13/afero" "io" "os" "path/filepath" + "syscall" ) +func setSolution(ctx context.Context, bin *string, dst *[]byte, solution problems.Solution) error { + file, err := solution.GetFile(ctx) + if err != nil { + return err + } + + if bin != nil { + *bin = file.Name + } + *dst, err = io.ReadAll(file.Source) + return errors.Join(err, file.Source.Close()) +} + type ACRunner struct{} func (a ACRunner) SetSolution(_ context.Context, _ problems.Solution) error { @@ -81,15 +98,7 @@ func (r *BasicRunner) getOutputFile() string { func (r *BasicRunner) SetSolution(ctx context.Context, solution problems.Solution) error { r.lang = solution.GetLanguage() - - file, err := solution.GetFile(ctx) - if err != nil { - return err - } - - r.binName = file.Name - r.bin, err = io.ReadAll(file.Source) - return errors.Join(err, file.Source.Close()) + return setSolution(ctx, &r.binName, &r.bin, solution) } func (r *BasicRunner) prepareIO(s sandbox.Sandbox, testcase *problems.Testcase) (io.ReadCloser, io.WriteCloser, error) { @@ -162,6 +171,25 @@ func (r *BasicRunner) setOutputExpectedOutput(s sandbox.Sandbox, testcase *probl return nil } +func mapVerdict(status *sandbox.Status, testcase *problems.Testcase) bool { + switch status.Verdict { + case sandbox.VerdictOK: + testcase.VerdictName = problems.VerdictAC // checker can overwrite it + return true + case sandbox.VerdictTL: + testcase.VerdictName = problems.VerdictTL + case sandbox.VerdictML: + testcase.VerdictName = problems.VerdictML + case sandbox.VerdictRE: + testcase.VerdictName = problems.VerdictRE + case sandbox.VerdictXX: + testcase.VerdictName = problems.VerdictXX + case sandbox.VerdictCE: + panic("solution should've been already compiled") + } + return false +} + func (r *BasicRunner) Run(ctx context.Context, sandboxProvider sandbox.Provider, testcase *problems.Testcase) error { s, err := sandboxProvider.Get() if err != nil { @@ -192,6 +220,9 @@ func (r *BasicRunner) Run(ctx context.Context, sandboxProvider sandbox.Provider, Name: r.binName, Source: io.NopCloser(bytes.NewBuffer(r.bin)), }, sandboxInput, sandboxOutput, testcase.TimeLimit, testcase.MemoryLimit) + if err != nil { + return err + } if sandboxOutput != nil { testcase.OutputPath = (sandboxOutput.(*os.File)).Name() @@ -212,26 +243,221 @@ func (r *BasicRunner) Run(ctx context.Context, sandboxProvider sandbox.Provider, return err } - switch status.Verdict { - case sandbox.VerdictOK: - testcase.VerdictName = problems.VerdictAC // checker can overwrite it - case sandbox.VerdictTL: - testcase.VerdictName = problems.VerdictTL + if !mapVerdict(status, testcase) || r.checker == nil { + return nil + } + return r.checker.Check(ctx, testcase) +} + +type ZipRunner struct { + checker problems.Checker + bin []byte +} + +func NewZipRunner(checker problems.Checker) *ZipRunner { + return &ZipRunner{checker: checker, bin: nil} +} + +func (z *ZipRunner) SetSolution(ctx context.Context, solution problems.Solution) error { + return setSolution(ctx, nil, &z.bin, solution) +} + +func (z *ZipRunner) Run(ctx context.Context, _ sandbox.Provider, testcase *problems.Testcase) error { + archive, err := zip.NewReader(bytes.NewReader(z.bin), int64(len(z.bin))) + if err != nil { return err - case sandbox.VerdictML: - testcase.VerdictName = problems.VerdictML + } + for _, f := range archive.File { + if f.Name == testcase.OutputPath { + fileHandle, err := f.Open() + if err != nil { + return err + } + defer func(fileHandle io.ReadCloser) { + _ = fileHandle.Close() + }(fileHandle) + + tempFile, err := os.CreateTemp("", "njudge_zip_input") + if err != nil { + return err + } + defer func(tempFile *os.File, name string) { + _ = tempFile.Close() + _ = os.Remove(name) + }(tempFile, tempFile.Name()) + + _, err = io.CopyN(tempFile, fileHandle, 32*1024*1024) // 32MiB limit + if err != nil && !errors.Is(err, io.EOF) { + return err + } + + testcase.OutputPath = tempFile.Name() + break + } + } + + if z.checker == nil { + return nil + } + return z.checker.Check(ctx, testcase) +} + +type InteractiveRunner struct { + lang language.Language + userBinName string + userBin []byte + + interactorBin []byte + + checker problems.Checker + + fs afero.Fs +} + +type InteractiveRunnerOption func(runner *InteractiveRunner) + +func InteractiveRunnerWithFs(fs afero.Fs) InteractiveRunnerOption { + return func(runner *InteractiveRunner) { + runner.fs = fs + } +} + +func NewInteractiveRunner(interactorBinary []byte, checker problems.Checker, opts ...InteractiveRunnerOption) *InteractiveRunner { + res := &InteractiveRunner{ + userBin: nil, + interactorBin: interactorBinary, + checker: checker, + fs: afero.NewOsFs(), + } + for _, opt := range opts { + opt(res) + } + return res +} + +func (r *InteractiveRunner) SetSolution(ctx context.Context, solution problems.Solution) error { + r.lang = solution.GetLanguage() + return setSolution(ctx, &r.userBinName, &r.userBin, solution) +} + +func (r *InteractiveRunner) getSandboxes(provider sandbox.Provider) (userSandbox, interactorSandbox sandbox.Sandbox, err error) { + userSandbox, err = provider.Get() + if err != nil { + return + } + interactorSandbox, err = provider.Get() + return +} + +func (r *InteractiveRunner) prepareFIFO(dir string, name string) (*os.File, error) { + if err := syscall.Mkfifo(filepath.Join(dir, name), 0666); err != nil { + return nil, err + } + return os.OpenFile(filepath.Join(dir, name), os.O_RDWR, 0666) +} + +func (r *InteractiveRunner) Run(ctx context.Context, sandboxProvider sandbox.Provider, testcase *problems.Testcase) error { + userSandbox, interactorSandbox, err := r.getSandboxes(sandboxProvider) + if err != nil { return err - case sandbox.VerdictRE: - testcase.VerdictName = problems.VerdictRE + } + defer func() { + _ = userSandbox.Cleanup(context.Background()) + _ = interactorSandbox.Cleanup(context.Background()) + sandboxProvider.Put(userSandbox) + sandboxProvider.Put(interactorSandbox) + }() + if err = userSandbox.Init(ctx); err != nil { return err - case sandbox.VerdictXX: - testcase.VerdictName = problems.VerdictXX + } + if err = interactorSandbox.Init(ctx); err != nil { return err - case sandbox.VerdictCE: - panic("solution should've been already compiled") } - if r.checker == nil { + if err = sandbox.CreateFile(interactorSandbox, sandbox.File{ + Name: "interactor", + Source: io.NopCloser(bytes.NewBuffer(r.interactorBin)), + }); err != nil { + return err + } + if err = interactorSandbox.MakeExecutable("interactor"); err != nil { + return err + } + + inputFile, err := r.fs.Open(testcase.InputPath) + if err != nil { + return err + } + if err = sandbox.CreateFile(interactorSandbox, sandbox.File{ + Name: "input", + Source: inputFile, + }); err != nil { + return err + } + + dir, err := os.MkdirTemp("", "njudge_interactive_runner") + if err != nil { + return err + } + defer func(path string) { + _ = os.RemoveAll(path) + }(dir) + + userStdin, err := r.prepareFIFO(dir, "fifo1") + if err != nil { + return err + } + defer func(userStdin *os.File) { + _ = userStdin.Close() + }(userStdin) + userStdout, err := r.prepareFIFO(dir, "fifo2") + if err != nil { + return err + } + defer func(userStdout *os.File) { + _ = userStdout.Close() + }(userStdout) + + var ( + userStatus, interactorStatus *sandbox.Status + userError, interactorError error + done = make(chan struct{}) + ) + + go func() { + userStatus, userError = r.lang.Run(ctx, userSandbox, sandbox.File{ + Name: r.userBinName, + Source: io.NopCloser(bytes.NewBuffer(r.userBin)), + }, userStdin, userStdout, testcase.TimeLimit, testcase.MemoryLimit) + done <- struct{}{} + }() + + interactorStatus, interactorError = interactorSandbox.Run(ctx, sandbox.RunConfig{ + RunID: "interactor", + TimeLimit: 2 * testcase.TimeLimit, + MemoryLimit: 1 * memory.GiB, + Stdin: userStdout, + Stdout: userStdin, + Stderr: os.Stderr, + InheritEnv: true, + WorkingDirectory: interactorSandbox.Pwd(), + }, "interactor", "input", "output") + <-done + + testcase.OutputPath = filepath.Join(interactorSandbox.Pwd(), "output") + testcase.TimeSpent = userStatus.Time + testcase.MemoryUsed = userStatus.Memory + + if userError != nil || interactorError != nil { + return errors.Join(userError, interactorError) + } + + if interactorStatus.Verdict != sandbox.VerdictOK { + testcase.VerdictName = problems.VerdictXX + return fmt.Errorf("interactor didn't return ok: %v", interactorStatus) + } + + if !mapVerdict(userStatus, testcase) || r.checker == nil { return nil } return r.checker.Check(ctx, testcase) diff --git a/pkg/problems/evaluation/run_test.go b/pkg/problems/evaluation/run_test.go index 323cf805..fd72efbc 100644 --- a/pkg/problems/evaluation/run_test.go +++ b/pkg/problems/evaluation/run_test.go @@ -1,8 +1,13 @@ package evaluation_test import ( + "archive/zip" + "bytes" "context" + "fmt" + "github.com/mraron/njudge/pkg/internal/testutils" "github.com/mraron/njudge/pkg/language/langs/python3" + zipLang "github.com/mraron/njudge/pkg/language/langs/zip" "github.com/mraron/njudge/pkg/language/sandbox" "github.com/mraron/njudge/pkg/problems" "github.com/mraron/njudge/pkg/problems/evaluation" @@ -10,6 +15,7 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "testing" + "time" ) func TestBasicRunner_Run(t *testing.T) { @@ -18,7 +24,15 @@ func TestBasicRunner_Run(t *testing.T) { sandboxProvider sandbox.Provider testcase *problems.Testcase } - s, err := sandbox.NewDummy() + var ( + s sandbox.Sandbox + err error + ) + if *testutils.UseIsolate { + s, err = sandbox.NewDummy() + } else { + s, err = sandbox.NewIsolate(444) + } assert.Nil(t, err) fs := afero.NewMemMapFs() @@ -179,7 +193,15 @@ with open('kimenet', 'w') as w: } func TestBasicRunnerMultipleRuns(t *testing.T) { - s, err := sandbox.NewDummy() + var ( + s sandbox.Sandbox + err error + ) + if *testutils.UseIsolate { + s, err = sandbox.NewDummy() + } else { + s, err = sandbox.NewIsolate(444) + } assert.Nil(t, err) fs := afero.NewMemMapFs() @@ -205,3 +227,142 @@ print(a+b+c, ' \n')`)) assert.Nil(t, br.Run(context.Background(), sandbox.NewSandboxProvider().Put(s), tc)) assert.Equal(t, problems.VerdictAC, tc.VerdictName) } + +func TestZipRunner_Run(t *testing.T) { + fs := afero.NewMemMapFs() + assert.Nil(t, afero.WriteFile(fs, "input", []byte("hello_world?\n"), 0644)) + assert.Nil(t, afero.WriteFile(fs, "answer", []byte("hello_world!\n"), 0644)) + + zipBuf := &bytes.Buffer{} + w := zip.NewWriter(zipBuf) + f, _ := w.Create("output1") + _, _ = f.Write([]byte("hello_world!")) + _ = w.Close() + + type args struct { + ctx context.Context + sandboxProvider sandbox.Provider + testcase *problems.Testcase + } + tests := []struct { + name string + solution problems.Solution + checker problems.Checker + args args + wantErr assert.ErrorAssertionFunc + wantVerdict problems.VerdictName + }{ + { + name: "zip_hello_world", + solution: evaluation.NewByteSolution(zipLang.Zip{}, zipBuf.Bytes()), + checker: checker.NewWhitediff(checker.WhiteDiffWithFs(fs, afero.NewOsFs())), + args: args{ + ctx: context.TODO(), + sandboxProvider: nil, + testcase: &problems.Testcase{ + Index: 1, + InputPath: "input", + OutputPath: "output1", + AnswerPath: "answer", + }, + }, + wantErr: assert.NoError, + wantVerdict: problems.VerdictAC, + }, + { + name: "zip_hello_world_wa", + solution: evaluation.NewByteSolution(zipLang.Zip{}, zipBuf.Bytes()), + checker: checker.NewWhitediff(checker.WhiteDiffWithFs(fs, afero.NewOsFs())), + args: args{ + ctx: context.TODO(), + sandboxProvider: nil, + testcase: &problems.Testcase{ + Index: 1, + InputPath: "input", + OutputPath: "output1", + AnswerPath: "input", // WA because of this + }, + }, + wantErr: assert.NoError, + wantVerdict: problems.VerdictWA, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := evaluation.NewZipRunner(tt.checker) + assert.Nil(t, z.SetSolution(context.TODO(), tt.solution)) + tt.wantErr(t, z.Run(tt.args.ctx, tt.args.sandboxProvider, tt.args.testcase), fmt.Sprintf("Run(%v, %v, %v)", tt.args.ctx, tt.args.sandboxProvider, tt.args.testcase)) + assert.Equal(t, tt.wantVerdict, tt.args.testcase.VerdictName) + }) + } +} + +func TestInteractiveRunner_Run(t *testing.T) { + type args struct { + ctx context.Context + sandboxProvider sandbox.Provider + testcase *problems.Testcase + } + var ( + s1, s2 sandbox.Sandbox + err error + ) + if *testutils.UseIsolate { + s1, err = sandbox.NewDummy() + assert.Nil(t, err) + s2, err = sandbox.NewDummy() + assert.Nil(t, err) + } else { + s1, err = sandbox.NewIsolate(444) + assert.Nil(t, err) + s2, err = sandbox.NewIsolate(445) + assert.Nil(t, err) + } + + fs := afero.NewMemMapFs() + assert.Nil(t, afero.WriteFile(fs, "input", []byte("11 12\n"), 0644)) + assert.Nil(t, afero.WriteFile(fs, "answer", []byte("23\n"), 0644)) + tests := []struct { + name string + solution problems.Solution + ir *evaluation.InteractiveRunner + args args + wantErr assert.ErrorAssertionFunc + wantVerdict problems.VerdictName + }{ + { + name: "aplusb_python_interactor", + solution: evaluation.NewByteSolution(python3.Python3{}, []byte(`a, b = list(map(int, input().split())) +print(a+b)`)), + ir: evaluation.NewInteractiveRunner([]byte(`#!/usr/bin/python3 +import sys +with open(sys.argv[1], 'r') as f: + a,b = list(map(int, f.readline().split())) + +print(a,b) +res = input() +with open(sys.argv[2], 'w') as f: + f.write(res)`), checker.NewWhitediff(checker.WhiteDiffWithFs(fs, afero.NewOsFs())), evaluation.InteractiveRunnerWithFs(fs)), + args: args{ + ctx: context.TODO(), + sandboxProvider: sandbox.NewSandboxProvider().Put(s1).Put(s2), + testcase: &problems.Testcase{ + Index: 1, + InputPath: "input", + OutputPath: "output", + AnswerPath: "answer", + TimeLimit: 1 * time.Second, + }, + }, + wantErr: assert.NoError, + wantVerdict: problems.VerdictAC, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Nil(t, tt.ir.SetSolution(tt.args.ctx, tt.solution)) + tt.wantErr(t, tt.ir.Run(tt.args.ctx, tt.args.sandboxProvider, tt.args.testcase), fmt.Sprintf("Run(%v, %v, %v)", tt.args.ctx, tt.args.sandboxProvider, tt.args.testcase)) + assert.Equal(t, tt.wantVerdict, tt.args.testcase.VerdictName) + }) + } +}