Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/iris/src/service/build/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ func (bu *BuildUnit) Setup(
Dir: bu.Dir,
Language: bu.ParsedLang,
})
// compileErr: sandbox service itself failed (e.g., timeout, internal error)
// compileResult.ExecResult.ErrorCode != 0: sandbox ran but user code has errors
// compileErr: compiler could not be started or the sandbox service failed
// compileResult.ExecResult.StatusCode != RUN_SUCCESS: compiler exited unsuccessfully or timed out
if compileErr != nil {
return &BuildUnitError{
Unit: bu.Name,
Expand Down
57 changes: 57 additions & 0 deletions apps/iris/src/service/build/unit_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package build

import (
"errors"
"path/filepath"
"testing"
"time"

"github.com/skkuding/codedang/apps/iris/src/service/file"
"github.com/skkuding/codedang/apps/iris/src/service/sandbox"
"github.com/skkuding/codedang/apps/iris/src/service/sandbox/judger"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -98,3 +101,57 @@ func TestBuildUnitRunSerializesSameUnit(t *testing.T) {
<-done
assert.False(t, concurrent, "same BuildUnit entered the sandbox concurrently")
}

type compilingSandbox struct {
*blockingSandbox
baseDir string
result sandbox.CompileResult
err error
}

func (s *compilingSandbox) MakeSrcPath(dir string, _ sandbox.Language) (string, error) {
return filepath.Join(s.baseDir, dir, "main.c"), nil
}

func (s *compilingSandbox) Compile(sandbox.CompileRequest) (sandbox.CompileResult, error) {
return s.result, s.err
}

func TestBuildUnitSetupClassifiesCompileFailure(t *testing.T) {
for _, tc := range []struct {
name string
result sandbox.CompileResult
err error
isUserError bool
}{
{
name: "compiler rejected source",
result: sandbox.CompileResult{
ExecResult: sandbox.ExecResult{StatusCode: sandbox.COMPILE_ERROR},
ErrOutput: "syntax error",
},
isUserError: true,
},
{
name: "compiler could not start",
err: errors.New("compiler execution failed"),
},
} {
t.Run(tc.name, func(t *testing.T) {
baseDir := t.TempDir()
unit := &BuildUnit{Code: "invalid source", Language: string(sandbox.CPP)}
fake := &compilingSandbox{
blockingSandbox: newBlockingSandbox(),
baseDir: baseDir,
result: tc.result,
err: tc.err,
}

buildErr := unit.Setup(0, 1, file.NewFileManager(baseDir), fake)

require.Error(t, buildErr)
assert.Equal(t, "compile", buildErr.Phase)
assert.Equal(t, tc.isUserError, buildErr.IsUserError)
})
}
}
33 changes: 14 additions & 19 deletions apps/iris/src/service/sandbox/judger/compiler.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package judger

import (
"bytes"
"fmt"

"context"
"errors"
"fmt"
"os"
"os/exec"
"time"
Expand Down Expand Up @@ -40,16 +39,6 @@ func (c *compiler) Compile(dto sandbox.CompileRequest) (sandbox.CompileResult, e
return sandbox.CompileResult{}, err
}

if execResult.StatusCode == sandbox.StatusCode(SYSTEM_ERROR) {
c.logger.Log(logger.ERROR, fmt.Sprintf("Compile failed: %+v", execResult))
data, err := c.file.ReadFile(constants.COMPILE_LOG_PATH)
if err != nil {
return sandbox.CompileResult{}, fmt.Errorf("failed to read output file: %w", err)
}
c.logger.Log(logger.ERROR, fmt.Sprintf("Compile Log: %s", string(data)))
return sandbox.CompileResult{}, fmt.Errorf("system error: %v", execResult)
}

compileResult := sandbox.CompileResult{}
compileResult.ExecResult = execResult

Expand All @@ -71,7 +60,7 @@ func (c *compiler) compileExec(args ExecArgs) (sandbox.ExecResult, error) {
outputFile, err := os.Create(args.OutputPath)
if err != nil {
return sandbox.ExecResult{
StatusCode: sandbox.StatusCode(SYSTEM_ERROR),
StatusCode: sandbox.SERVER_ERROR,
}, err
}
defer outputFile.Close()
Expand All @@ -81,9 +70,8 @@ func (c *compiler) compileExec(args ExecArgs) (sandbox.ExecResult, error) {

cmd := exec.CommandContext(ctx, args.ExePath, args.Args...)
cmd.Env = append(cmd.Env, env)

var stderr bytes.Buffer
cmd.Stderr = &stderr
cmd.Stdout = outputFile
cmd.Stderr = outputFile

startTime := time.Now()
err = cmd.Run()
Expand All @@ -95,9 +83,16 @@ func (c *compiler) compileExec(args ExecArgs) (sandbox.ExecResult, error) {
}

if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && exitErr.ExitCode() >= 0 {
return sandbox.ExecResult{
ExitCode: exitErr.ExitCode(),
StatusCode: sandbox.COMPILE_ERROR,
}, nil
}
return sandbox.ExecResult{
StatusCode: sandbox.StatusCode(SYSTEM_ERROR),
}, fmt.Errorf("%s", stderr.String())
StatusCode: sandbox.SERVER_ERROR,
}, fmt.Errorf("compiler execution failed: %w", err)
}

realTimeSpentMS := int(time.Since(startTime).Milliseconds())
Expand Down
75 changes: 75 additions & 0 deletions apps/iris/src/service/sandbox/judger/compiler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package judger

import (
"os"
"path/filepath"
"testing"

"github.com/skkuding/codedang/apps/iris/src/common/constants"
"github.com/skkuding/codedang/apps/iris/src/service/file"
"github.com/skkuding/codedang/apps/iris/src/service/sandbox"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type fixedCompileArgs struct {
sandbox.LangConfig[JudgerConfig, ExecArgs]
args ExecArgs
}

func (f fixedCompileArgs) ToCompileExecArgs(string, sandbox.Language) (ExecArgs, error) {
return f.args, nil
}

func TestCompileReturnsCompilerFailureAndDiagnostic(t *testing.T) {
baseDir := t.TempDir()
dir := "source"
require.NoError(t, os.Mkdir(filepath.Join(baseDir, dir), 0700))
c := &compiler{
langConfig: fixedCompileArgs{args: ExecArgs{
ExePath: "/bin/sh",
Args: []string{"-c", "printf 'syntax error\\n' >&2; exit 1"},
OutputPath: filepath.Join(baseDir, dir, constants.COMPILE_OUT_FILE),
MaxRealTime: 1000,
}},
file: file.NewFileManager(baseDir),
}

result, err := c.Compile(sandbox.CompileRequest{Dir: dir, Language: sandbox.CPP})

require.NoError(t, err)
assert.Equal(t, sandbox.COMPILE_ERROR, result.ExecResult.StatusCode)
assert.Equal(t, 1, result.ExecResult.ExitCode)
assert.Equal(t, "syntax error\n", result.ErrOutput)
}

func TestCompileExecClassifiesCompilerExit(t *testing.T) {
outputPath := filepath.Join(t.TempDir(), "compile.out")
c := &compiler{}

result, err := c.compileExec(ExecArgs{
ExePath: "/bin/sh",
Args: []string{"-c", "printf 'syntax error\\n' >&2; exit 1"},
OutputPath: outputPath,
MaxRealTime: 1000,
})

require.NoError(t, err)
assert.Equal(t, sandbox.COMPILE_ERROR, result.StatusCode)
assert.Equal(t, 1, result.ExitCode)
output, err := os.ReadFile(outputPath)
require.NoError(t, err)
assert.Equal(t, "syntax error\n", string(output))
}

func TestCompileExecReportsStartFailure(t *testing.T) {
c := &compiler{}
result, err := c.compileExec(ExecArgs{
ExePath: filepath.Join(t.TempDir(), "missing-compiler"),
OutputPath: filepath.Join(t.TempDir(), "compile.out"),
MaxRealTime: 1000,
})

require.Error(t, err)
assert.Equal(t, sandbox.SERVER_ERROR, result.StatusCode)
}
Loading