Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00e1c9195c | |||
| 9b9a790618 |
@@ -2,8 +2,6 @@ name: judge
|
|||||||
run-name: "Sum tests (${{ inputs.student_url || github.repository }})"
|
run-name: "Sum tests (${{ inputs.student_url || github.repository }})"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
pull_request:
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
student_url:
|
student_url:
|
||||||
@@ -143,7 +141,7 @@ jobs:
|
|||||||
# runner auto-detects .exe suffix on Windows
|
# runner auto-detects .exe suffix on Windows
|
||||||
judge "$SUITE_FILE" . --json $WRAPPER_ARG > "$GITHUB_WORKSPACE/${REPORT_NAME}.json" || true
|
judge "$SUITE_FILE" . --json $WRAPPER_ARG > "$GITHUB_WORKSPACE/${REPORT_NAME}.json" || true
|
||||||
|
|
||||||
judge "$SUITE_FILE" . $WRAPPER_ARG || true
|
judge "$SUITE_FILE" . $WRAPPER_ARG
|
||||||
|
|
||||||
- name: Upload report
|
- name: Upload report
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
|
|||||||
@@ -18,14 +18,24 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.22'
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
- name: Cross-compile
|
- name: Cross-compile
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
tag="${GITHUB_REF#refs/tags/}"
|
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o "dist/judge-linux-amd64" ./cmd/cli
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o "dist/judge-linux-amd64" ./cmd/cli
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o "dist/judge-windows-amd64.exe" ./cmd/cli
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o "dist/judge-windows-amd64.exe" ./cmd/cli
|
||||||
ls -la dist/
|
|
||||||
|
- name: Build VS Code extension
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
npm install -g @vscode/vsce
|
||||||
|
cd editor/vscode-jdg
|
||||||
|
vsce package -o "../../dist/jdg-language-${{ github.ref_name }}.vsix"
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: https://gitea.com/actions/gitea-release-action@main
|
uses: https://gitea.com/actions/gitea-release-action@main
|
||||||
@@ -33,5 +43,6 @@ jobs:
|
|||||||
files: |-
|
files: |-
|
||||||
dist/judge-linux-amd64
|
dist/judge-linux-amd64
|
||||||
dist/judge-windows-amd64.exe
|
dist/judge-windows-amd64.exe
|
||||||
|
dist/jdg-language-${{ github.ref_name }}.vsix
|
||||||
api_key: ${{ secrets.RELEASE_TOKEN }}
|
api_key: ${{ secrets.RELEASE_TOKEN }}
|
||||||
title: ${{ github.ref_name }}
|
title: ${{ github.ref_name }}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type File struct {
|
|||||||
BuildDarwin string
|
BuildDarwin string
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
Binary string // executable name produced by build (default: solution)
|
Binary string // executable name produced by build (default: solution)
|
||||||
|
Sources string // glob pattern for source files, expanded as $SOURCES in build
|
||||||
|
|
||||||
NormalizeCRLF bool // strip \r before matching stdout/stderr/outFiles
|
NormalizeCRLF bool // strip \r before matching stdout/stderr/outFiles
|
||||||
TrimTrailingWS bool // trim trailing whitespace on each line before matching
|
TrimTrailingWS bool // trim trailing whitespace on each line before matching
|
||||||
|
|||||||
@@ -128,6 +128,17 @@ func (p *Parser) parseFile() (*File, error) {
|
|||||||
}
|
}
|
||||||
f.Binary = s.Value
|
f.Binary = s.Value
|
||||||
|
|
||||||
|
case "sources":
|
||||||
|
p.advance()
|
||||||
|
if _, err := p.expect(TOKEN_ASSIGN); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s, err := p.expect(TOKEN_STRING)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.Sources = s.Value
|
||||||
|
|
||||||
case "normalize_crlf":
|
case "normalize_crlf":
|
||||||
p.advance()
|
p.advance()
|
||||||
if _, err := p.expect(TOKEN_ASSIGN); err != nil {
|
if _, err := p.expect(TOKEN_ASSIGN); err != nil {
|
||||||
|
|||||||
BIN
editor/.DS_Store
vendored
BIN
editor/.DS_Store
vendored
Binary file not shown.
@@ -53,7 +53,7 @@
|
|||||||
},
|
},
|
||||||
"top-keywords": {
|
"top-keywords": {
|
||||||
"name": "keyword.control.jdg",
|
"name": "keyword.control.jdg",
|
||||||
"match": "\\b(?:build|build_linux|build_windows|build_darwin|binary|timeout|normalize_crlf|trim_trailing_ws|group|test|pattern|env|file|outFile)\\b"
|
"match": "\\b(?:build|build_linux|build_windows|build_darwin|binary|sources|timeout|normalize_crlf|trim_trailing_ws|group|test|pattern|env|file|outFile)\\b"
|
||||||
},
|
},
|
||||||
"block-keywords": {
|
"block-keywords": {
|
||||||
"name": "variable.parameter.jdg",
|
"name": "variable.parameter.jdg",
|
||||||
|
|||||||
18
runner/process_unix.go
Normal file
18
runner/process_unix.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package runner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setProcessGroup(cmd *exec.Cmd) {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func killProcessGroup(cmd *exec.Cmd) {
|
||||||
|
if cmd.Process != nil {
|
||||||
|
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
runner/process_windows.go
Normal file
20
runner/process_windows.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package runner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setProcessGroup(cmd *exec.Cmd) {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func killProcessGroup(cmd *exec.Cmd) {
|
||||||
|
if cmd.Process != nil {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -119,9 +119,48 @@ func (r *Runner) buildCommand() string {
|
|||||||
return "go build -o solution ."
|
return "go build -o solution ."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Runner) findSources() (string, error) {
|
||||||
|
if r.file.Sources == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
var files []string
|
||||||
|
err := filepath.Walk(r.cfg.WorkDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
if info.Name() == ".git" || info.Name() == ".gitea" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
matched, _ := filepath.Match(r.file.Sources, info.Name())
|
||||||
|
if matched {
|
||||||
|
rel, _ := filepath.Rel(r.cfg.WorkDir, path)
|
||||||
|
files = append(files, filepath.ToSlash(rel))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("source discovery: %w", err)
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
return "", fmt.Errorf("no files matching %q found", r.file.Sources)
|
||||||
|
}
|
||||||
|
return strings.Join(files, " "), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Runner) build() (string, error) {
|
func (r *Runner) build() (string, error) {
|
||||||
buildCmd := r.buildCommand()
|
buildCmd := r.buildCommand()
|
||||||
|
|
||||||
|
sources, err := r.findSources()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if sources != "" {
|
||||||
|
buildCmd = strings.ReplaceAll(buildCmd, "$SOURCES", sources)
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if r.file.Timeout > 0 {
|
if r.file.Timeout > 0 {
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
@@ -131,6 +170,7 @@ func (r *Runner) build() (string, error) {
|
|||||||
|
|
||||||
cmd := shellCommand(ctx, buildCmd)
|
cmd := shellCommand(ctx, buildCmd)
|
||||||
cmd.Dir = r.cfg.WorkDir
|
cmd.Dir = r.cfg.WorkDir
|
||||||
|
setProcessGroup(cmd)
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
|
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
@@ -138,6 +178,7 @@ func (r *Runner) build() (string, error) {
|
|||||||
cmd.Stderr = &out
|
cmd.Stderr = &out
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
|
killProcessGroup(cmd)
|
||||||
return out.String(), fmt.Errorf("build failed: %w\n%s", err, out.String())
|
return out.String(), fmt.Errorf("build failed: %w\n%s", err, out.String())
|
||||||
}
|
}
|
||||||
return out.String(), nil
|
return out.String(), nil
|
||||||
@@ -249,8 +290,13 @@ func (r *Runner) runTest(t *dsl.Test) *TestResult {
|
|||||||
wrapper = t.Wrapper
|
wrapper = t.Wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := buildExecCmd(ctx, wrapper, r.binary, t.Args)
|
args := t.Args
|
||||||
|
if wrapper != "" {
|
||||||
|
args = absoluteArgs(tmpDir, args)
|
||||||
|
}
|
||||||
|
cmd := buildExecCmd(ctx, wrapper, r.binary, args)
|
||||||
cmd.Dir = tmpDir
|
cmd.Dir = tmpDir
|
||||||
|
setProcessGroup(cmd)
|
||||||
|
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
cmd.Env = append(cmd.Env, "LC_ALL=C", "LANG=C")
|
cmd.Env = append(cmd.Env, "LC_ALL=C", "LANG=C")
|
||||||
@@ -271,6 +317,10 @@ func (r *Runner) runTest(t *dsl.Test) *TestResult {
|
|||||||
runErr := cmd.Run()
|
runErr := cmd.Run()
|
||||||
tr.Elapsed = time.Since(start)
|
tr.Elapsed = time.Since(start)
|
||||||
|
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
killProcessGroup(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
tr.ActualStdout = normalizeOutput(stdout.String(), r.file)
|
tr.ActualStdout = normalizeOutput(stdout.String(), r.file)
|
||||||
tr.ActualStderr = normalizeOutput(stderr.String(), r.file)
|
tr.ActualStderr = normalizeOutput(stderr.String(), r.file)
|
||||||
|
|
||||||
@@ -327,6 +377,18 @@ func (r *Runner) runTest(t *dsl.Test) *TestResult {
|
|||||||
return tr
|
return tr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func absoluteArgs(dir string, args []string) []string {
|
||||||
|
out := make([]string, len(args))
|
||||||
|
for i, a := range args {
|
||||||
|
if !filepath.IsAbs(a) {
|
||||||
|
out[i] = filepath.Join(dir, a)
|
||||||
|
} else {
|
||||||
|
out[i] = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func buildExecCmd(ctx context.Context, wrapper, binary string, args []string) *exec.Cmd {
|
func buildExecCmd(ctx context.Context, wrapper, binary string, args []string) *exec.Cmd {
|
||||||
if wrapper == "" {
|
if wrapper == "" {
|
||||||
return exec.CommandContext(ctx, binary, args...)
|
return exec.CommandContext(ctx, binary, args...)
|
||||||
|
|||||||
Reference in New Issue
Block a user