Initial implementation of new runner architecture
This commit is contained in:
parent
f5e50b6c6d
commit
9fd93f91a1
9 changed files with 269 additions and 168 deletions
47
challenges/2021/10-syntaxScoring/runtime-wrapper.py
Normal file
47
challenges/2021/10-syntaxScoring/runtime-wrapper.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from py import Challenge
|
||||
|
||||
import time
|
||||
import json
|
||||
|
||||
TASKS_STR = input()
|
||||
TASKS = json.loads(TASKS_STR)
|
||||
|
||||
def send_result(task_id, ok, output, duration):
|
||||
print(json.dumps({
|
||||
"task_id": task_id,
|
||||
"ok": ok,
|
||||
"output": str(output) if output is not None else "",
|
||||
"duration": float(duration),
|
||||
}), flush=True)
|
||||
|
||||
for task in TASKS:
|
||||
taskPart = task["part"]
|
||||
task_id = task["task_id"]
|
||||
|
||||
run = None
|
||||
|
||||
if taskPart == 1:
|
||||
run = lambda: Challenge.one(task["input"])
|
||||
elif taskPart == 2:
|
||||
run = lambda: Challenge.two(task["input"])
|
||||
elif taskPart == 3:
|
||||
run = lambda: Challenge.vis(task["input"], task["output_dir"])
|
||||
else:
|
||||
send_result(task_id, False, "unknown task part", 0)
|
||||
continue
|
||||
|
||||
start_time = time.time()
|
||||
res = None
|
||||
err = None
|
||||
try:
|
||||
res = run()
|
||||
except Exception as e:
|
||||
err = f"{type(e)}: {e}"
|
||||
end_time = time.time()
|
||||
|
||||
running_time = end_time-start_time
|
||||
|
||||
if err is not None:
|
||||
send_result(task_id, False, err, running_time)
|
||||
else:
|
||||
send_result(task_id, True, res, running_time)
|
|
@ -6,10 +6,8 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/alexflint/go-arg"
|
||||
"github.com/codemicro/adventOfCode/runtime/benchmark"
|
||||
"github.com/codemicro/adventOfCode/runtime/challenge"
|
||||
"github.com/codemicro/adventOfCode/runtime/runners"
|
||||
au "github.com/logrusorgru/aurora"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -57,7 +55,7 @@ func run() error {
|
|||
challengeInputString := string(challengeInput)
|
||||
|
||||
if args.Benchmark {
|
||||
return benchmark.Run(selectedChallenge, challengeInputString, args.BenchmarkN)
|
||||
//return benchmark.Run(selectedChallenge, challengeInputString, args.BenchmarkN)
|
||||
}
|
||||
|
||||
// List and select implementations
|
||||
|
@ -66,60 +64,24 @@ func run() error {
|
|||
return err
|
||||
}
|
||||
|
||||
runner := runners.Available[selectedImplementation](selectedChallenge.Dir)
|
||||
|
||||
lookupTable := make(taskLookupTable)
|
||||
|
||||
if args.Visualise {
|
||||
id := "vis"
|
||||
runner.Queue(&runners.Task{
|
||||
TaskID: id,
|
||||
Part: runners.Visualise,
|
||||
Input: challengeInputString,
|
||||
OutputDir: ".", // directory the runner is run in, which is the challenge directory
|
||||
})
|
||||
|
||||
lookupTable[id] = func(r *runners.Result) {
|
||||
|
||||
fmt.Print(au.Bold("Visualisation: "))
|
||||
|
||||
var status string
|
||||
var followUpText string
|
||||
if !r.Ok {
|
||||
status = incompleteLabel
|
||||
followUpText = "saying \"" + r.Output + "\""
|
||||
} else {
|
||||
status = passLabel
|
||||
}
|
||||
|
||||
if followUpText == "" {
|
||||
followUpText = fmt.Sprintf("in %.4f seconds", r.Duration)
|
||||
}
|
||||
|
||||
fmt.Print(status)
|
||||
fmt.Println(au.Gray(10, " "+followUpText))
|
||||
}
|
||||
|
||||
} else {
|
||||
setupTestTasks(challengeInfo, runner, &lookupTable)
|
||||
if !args.TestOnly {
|
||||
setupMainTasks(challengeInputString, runner, &lookupTable)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
r, cleanupFn := runner.Run()
|
||||
for roe := range r {
|
||||
if roe.Error != nil {
|
||||
return roe.Error
|
||||
}
|
||||
// fmt.Println(*roe.Result)
|
||||
lookupTable[roe.Result.TaskID](roe.Result)
|
||||
runner := runners.Available[selectedImplementation](selectedChallenge.Dir)
|
||||
if err := runner.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = runner.Stop()
|
||||
_ = runner.Cleanup()
|
||||
}()
|
||||
|
||||
|
||||
if err := runTests(runner, challengeInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cleanupFn != nil {
|
||||
cleanupFn()
|
||||
if err := runMainTasks(runner, challengeInputString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -5,9 +5,11 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
au "github.com/logrusorgru/aurora"
|
||||
)
|
||||
|
@ -68,6 +70,30 @@ func (c *customWriter) GetEntry() ([]byte, error) {
|
|||
return x, nil
|
||||
}
|
||||
|
||||
func setupBuffers(cmd *exec.Cmd) (io.WriteCloser, error) {
|
||||
stdoutWriter := &customWriter{}
|
||||
cmd.Stdout = stdoutWriter
|
||||
cmd.Stderr = new(bytes.Buffer)
|
||||
return cmd.StdinPipe()
|
||||
}
|
||||
|
||||
func checkWait(cmd *exec.Cmd) ([]byte, error) {
|
||||
c := cmd.Stdout.(*customWriter)
|
||||
for {
|
||||
e, err := c.GetEntry()
|
||||
if err == nil {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
if cmd.ProcessState != nil {
|
||||
// this is only populated after program exit - we have an issue
|
||||
return nil, fmt.Errorf("run failed with exit code %d: %s", cmd.ProcessState.ExitCode(), cmd.Stderr.(*bytes.Buffer).String())
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
}
|
||||
|
||||
func readResultsFromCommand(cmd *exec.Cmd) chan ResultOrError {
|
||||
|
||||
stdoutWriter := &customWriter{}
|
||||
|
|
|
@ -3,8 +3,8 @@ from py import Challenge
|
|||
import time
|
||||
import json
|
||||
|
||||
TASKS_STR = input()
|
||||
TASKS = json.loads(TASKS_STR)
|
||||
# TASKS_STR = input()
|
||||
# TASKS = json.loads(TASKS_STR)
|
||||
|
||||
def send_result(task_id, ok, output, duration):
|
||||
print(json.dumps({
|
||||
|
@ -14,7 +14,8 @@ def send_result(task_id, ok, output, duration):
|
|||
"duration": float(duration),
|
||||
}), flush=True)
|
||||
|
||||
for task in TASKS:
|
||||
while True:
|
||||
task = json.loads(input())
|
||||
taskPart = task["part"]
|
||||
task_id = task["task_id"]
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package runners
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
au "github.com/logrusorgru/aurora"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -12,11 +13,16 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
const python3Installation = "python3"
|
||||
const (
|
||||
python3Installation = "python3"
|
||||
pythonWrapperFilename = "runtime-wrapper.py"
|
||||
)
|
||||
|
||||
type pythonRunner struct {
|
||||
dir string
|
||||
tasks []*Task
|
||||
dir string
|
||||
cmd *exec.Cmd
|
||||
wrapperFilepath string
|
||||
stdin io.WriteCloser
|
||||
}
|
||||
|
||||
func newPythonRunner(dir string) Runner {
|
||||
|
@ -25,56 +31,82 @@ func newPythonRunner(dir string) Runner {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *pythonRunner) Queue(task *Task) {
|
||||
p.tasks = append(p.tasks, task)
|
||||
}
|
||||
|
||||
//go:embed interface/python.py
|
||||
var pythonInterface []byte
|
||||
|
||||
func (p *pythonRunner) Run() (chan ResultOrError, func()) {
|
||||
|
||||
wrapperFilename := "runtime-wrapper.py"
|
||||
wrapperFilepath := filepath.Join(p.dir, wrapperFilename)
|
||||
|
||||
// Generate interaction data
|
||||
taskJSON, err := json.Marshal(p.tasks)
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
}
|
||||
func (p *pythonRunner) Start() error {
|
||||
p.wrapperFilepath = filepath.Join(p.dir, pythonWrapperFilename)
|
||||
|
||||
// Save interaction code
|
||||
err = ioutil.WriteFile(wrapperFilepath, pythonInterface, 0644)
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
if err := ioutil.WriteFile(p.wrapperFilepath, pythonInterface, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sort out PYTHONPATH
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
return err
|
||||
}
|
||||
|
||||
absDir, err := filepath.Abs(p.dir)
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
return err
|
||||
}
|
||||
|
||||
pythonPathVar := strings.Join([]string{
|
||||
filepath.Join(cwd, "lib"),
|
||||
filepath.Join(absDir, "py"),
|
||||
filepath.Join(cwd, "lib"), // so we can use aocpy
|
||||
filepath.Join(absDir, "py"), // so we can import stuff in the challenge directory
|
||||
}, ":")
|
||||
|
||||
fmt.Println("Running...")
|
||||
p.cmd = exec.Command(python3Installation, "-B", pythonWrapperFilename) // -B prevents .pyc files from being written
|
||||
p.cmd.Env = append(p.cmd.Env, "PYTHONPATH="+pythonPathVar)
|
||||
p.cmd.Dir = p.dir
|
||||
|
||||
// Run Python and gather output
|
||||
cmd := exec.Command(python3Installation, "-B", wrapperFilename) // -B prevents .pyc files from being written
|
||||
cmd.Env = append(cmd.Env, "PYTHONPATH="+pythonPathVar) // this allows the aocpy lib to be imported
|
||||
cmd.Dir = p.dir
|
||||
|
||||
cmd.Stdin = bytes.NewReader(append(taskJSON, '\n'))
|
||||
|
||||
return readResultsFromCommand(cmd), func() {
|
||||
// Remove leftover files
|
||||
_ = os.Remove(wrapperFilepath)
|
||||
if stdin, err := setupBuffers(p.cmd); err != nil {
|
||||
return err
|
||||
} else {
|
||||
p.stdin = stdin
|
||||
}
|
||||
|
||||
return p.cmd.Start()
|
||||
}
|
||||
|
||||
func (p *pythonRunner) Stop() error {
|
||||
if p.cmd == nil || p.cmd.Process == nil {
|
||||
return nil
|
||||
}
|
||||
return p.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func (p *pythonRunner) Cleanup() error {
|
||||
if p.wrapperFilepath == "" {
|
||||
return nil
|
||||
}
|
||||
_ = os.Remove(p.wrapperFilepath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pythonRunner) Run(task *Task) (*Result, error) {
|
||||
taskJSON, err := json.Marshal(task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _ = p.stdin.Write(append(taskJSON, '\n'))
|
||||
|
||||
res := new(Result)
|
||||
for {
|
||||
inp, err := checkWait(p.cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(inp, res)
|
||||
if err != nil {
|
||||
// echo anything that won't parse to stdout (this lets us add debug print statements)
|
||||
fmt.Printf("[%s] %v\n", au.BrightRed("DBG"), strings.TrimSpace(string(inp)))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
@ -24,12 +24,12 @@ type RunnerCreator func(dir string) Runner
|
|||
|
||||
var Available = map[string]RunnerCreator{
|
||||
"py": newPythonRunner,
|
||||
"go": newGolangRunner,
|
||||
"nim": newNimRunner,
|
||||
//"go": newGolangRunner,
|
||||
//"nim": newNimRunner,
|
||||
}
|
||||
|
||||
var RunnerNames = map[string]string{
|
||||
"py": "Python",
|
||||
"go": "Golang",
|
||||
"nim": "Nim",
|
||||
//"go": "Golang",
|
||||
//"nim": "Nim",
|
||||
}
|
177
runtime/tasks.go
177
runtime/tasks.go
|
@ -2,6 +2,8 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/codemicro/adventOfCode/runtime/challenge"
|
||||
"github.com/codemicro/adventOfCode/runtime/runners"
|
||||
|
@ -16,86 +18,117 @@ var (
|
|||
incompleteLabel = au.BgBrightRed("did not complete").String()
|
||||
)
|
||||
|
||||
func setupTestTasks(info *challenge.Info, runner runners.Runner, table *taskLookupTable) {
|
||||
|
||||
st := func(part runners.Part, testCases []*challenge.TestCase, runner runners.Runner, table *taskLookupTable) {
|
||||
for i, testCase := range testCases {
|
||||
|
||||
// loop variables change, remember? :P
|
||||
i := i
|
||||
testCase := testCase
|
||||
|
||||
id := fmt.Sprintf("test.%d.%d", part, i)
|
||||
runner.Queue(&runners.Task{
|
||||
TaskID: id,
|
||||
Part: part,
|
||||
Input: testCase.Input,
|
||||
})
|
||||
|
||||
(*table)[id] = func(r *runners.Result) {
|
||||
|
||||
passed := r.Output == testCase.Expected
|
||||
|
||||
fmt.Print(au.Bold(fmt.Sprintf("Test %s: ",
|
||||
au.BrightBlue(fmt.Sprintf("%d.%d", part, i)),
|
||||
)))
|
||||
|
||||
var status string
|
||||
var followUpText string
|
||||
if !r.Ok {
|
||||
status = incompleteLabel
|
||||
followUpText = "saying \"" + r.Output + "\""
|
||||
} else if passed {
|
||||
status = passLabel
|
||||
} else {
|
||||
status = failLabel
|
||||
}
|
||||
|
||||
if followUpText == "" {
|
||||
followUpText = fmt.Sprintf("in %.4f seconds", r.Duration)
|
||||
}
|
||||
|
||||
fmt.Print(status)
|
||||
fmt.Println(au.Gray(10, " "+followUpText))
|
||||
|
||||
if !passed && r.Ok {
|
||||
fmt.Printf(" └ Expected %s, got %s\n", au.BrightBlue(testCase.Expected), au.BrightBlue(r.Output))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
st(runners.PartOne, info.TestCases.One, runner, table)
|
||||
st(runners.PartTwo, info.TestCases.Two, runner, table)
|
||||
func makeTestID(part runners.Part, n int) string {
|
||||
return fmt.Sprintf("test.%d.%d", part, n)
|
||||
}
|
||||
|
||||
func setupMainTasks(input string, runner runners.Runner, table *taskLookupTable) {
|
||||
func parseTestID(x string) (runners.Part, int) {
|
||||
y := strings.Split(x, ".")
|
||||
p, _ := strconv.Atoi(y[1])
|
||||
n, _ := strconv.Atoi(y[2])
|
||||
return runners.Part(p), n
|
||||
}
|
||||
|
||||
func makeMainID(part runners.Part) string {
|
||||
return fmt.Sprintf("main.%d", part)
|
||||
}
|
||||
|
||||
func parseMainID(x string) runners.Part {
|
||||
y := strings.Split(x, ".")
|
||||
p, _ := strconv.Atoi(y[1])
|
||||
return runners.Part(p)
|
||||
}
|
||||
|
||||
func runTests(runner runners.Runner, info *challenge.Info) error {
|
||||
for i, testCase := range info.TestCases.One {
|
||||
id := makeTestID(runners.PartOne, i)
|
||||
result, err := runner.Run(&runners.Task{
|
||||
TaskID: id,
|
||||
Part: runners.PartOne,
|
||||
Input: testCase.Input,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handleTestResult(result, testCase)
|
||||
}
|
||||
|
||||
for i, testCase := range info.TestCases.Two {
|
||||
id := makeTestID(runners.PartTwo, i)
|
||||
result, err := runner.Run(&runners.Task{
|
||||
TaskID: id,
|
||||
Part: runners.PartTwo,
|
||||
Input: testCase.Input,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handleTestResult(result, testCase)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleTestResult(r *runners.Result, testCase *challenge.TestCase) {
|
||||
part, n := parseTestID(r.TaskID)
|
||||
|
||||
fmt.Print(au.Bold(fmt.Sprintf("Test %s: ",
|
||||
au.BrightBlue(fmt.Sprintf("%d.%d", part, n)),
|
||||
)))
|
||||
|
||||
passed := r.Output == testCase.Expected
|
||||
|
||||
var status string
|
||||
var followUpText string
|
||||
if !r.Ok {
|
||||
status = incompleteLabel
|
||||
followUpText = "saying \"" + r.Output + "\""
|
||||
} else if passed {
|
||||
status = passLabel
|
||||
} else {
|
||||
status = failLabel
|
||||
}
|
||||
|
||||
if followUpText == "" {
|
||||
followUpText = fmt.Sprintf("in %.4f seconds", r.Duration)
|
||||
}
|
||||
|
||||
fmt.Print(status)
|
||||
fmt.Println(au.Gray(10, " "+followUpText))
|
||||
|
||||
if !passed && r.Ok {
|
||||
fmt.Printf(" └ Expected %s, got %s\n", au.BrightBlue(testCase.Expected), au.BrightBlue(r.Output))
|
||||
}
|
||||
}
|
||||
|
||||
func runMainTasks(runner runners.Runner, input string) error {
|
||||
for part := runners.PartOne; part <= runners.PartTwo; part += 1 {
|
||||
|
||||
part := part // loop variables need to be copied be used elsewhere to be used in function below, otherwise they
|
||||
// become rather wrong
|
||||
|
||||
id := fmt.Sprintf("main.%d", part)
|
||||
runner.Queue(&runners.Task{
|
||||
id := makeMainID(part)
|
||||
result, err := runner.Run(&runners.Task{
|
||||
TaskID: id,
|
||||
Part: part,
|
||||
Input: input,
|
||||
})
|
||||
|
||||
(*table)[id] = func(r *runners.Result) {
|
||||
|
||||
fmt.Print(au.Bold(fmt.Sprintf("Part %d: ", au.Yellow(part))))
|
||||
|
||||
if !r.Ok {
|
||||
fmt.Print(incompleteLabel)
|
||||
fmt.Println(au.Gray(10, " saying \""+r.Output+"\""))
|
||||
} else {
|
||||
fmt.Print(au.BrightBlue(r.Output))
|
||||
fmt.Println(au.Gray(10, fmt.Sprintf(" in %.4f seconds", r.Duration)))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
handleMainResult(result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMainResult(r *runners.Result) {
|
||||
part := parseMainID(r.TaskID)
|
||||
|
||||
fmt.Print(au.Bold(fmt.Sprintf("Part %d: ", au.Yellow(part))))
|
||||
|
||||
if !r.Ok {
|
||||
fmt.Print(incompleteLabel)
|
||||
fmt.Println(au.Gray(10, " saying \""+r.Output+"\""))
|
||||
} else {
|
||||
fmt.Print(au.BrightBlue(r.Output))
|
||||
fmt.Println(au.Gray(10, fmt.Sprintf(" in %.4f seconds", r.Duration)))
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue