Merge branch 'feat/queueless'
This commit is contained in:
commit
a1d2136fad
12 changed files with 477 additions and 266 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)
|
|
@ -107,18 +107,19 @@ type kv struct {
|
|||
|
||||
func benchmarkImplementation(implementation string, dir string, inputString string, numberRuns int) (*values, error) {
|
||||
|
||||
var results []*runners.Result
|
||||
var (
|
||||
tasks []*runners.Task
|
||||
results []*runners.Result
|
||||
)
|
||||
|
||||
runner := runners.Available[implementation](dir)
|
||||
for i := 0; i < numberRuns; i++ {
|
||||
|
||||
runner.Queue(&runners.Task{
|
||||
tasks = append(tasks, &runners.Task{
|
||||
TaskID: makeBenchmarkID(runners.PartOne, i),
|
||||
Part: runners.PartOne,
|
||||
Input: inputString,
|
||||
})
|
||||
|
||||
runner.Queue(&runners.Task{
|
||||
}, &runners.Task{
|
||||
TaskID: makeBenchmarkID(runners.PartTwo, i),
|
||||
Part: runners.PartTwo,
|
||||
Input: inputString,
|
||||
|
@ -133,14 +134,21 @@ func benchmarkImplementation(implementation string, dir string, inputString stri
|
|||
),
|
||||
)
|
||||
|
||||
res, cleanup := runner.Run()
|
||||
if err := runner.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = runner.Stop()
|
||||
_ = runner.Cleanup()
|
||||
}()
|
||||
|
||||
for roe := range res {
|
||||
if roe.Error != nil {
|
||||
for _, task := range tasks {
|
||||
res, err := runner.Run(task)
|
||||
if err != nil {
|
||||
_ = pb.Close()
|
||||
return nil, roe.Error
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, roe.Result)
|
||||
results = append(results, res)
|
||||
_ = pb.Add(1)
|
||||
}
|
||||
|
||||
|
@ -160,10 +168,6 @@ func benchmarkImplementation(implementation string, dir string, inputString stri
|
|||
}
|
||||
}
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup()
|
||||
}
|
||||
|
||||
return &values{
|
||||
implementation: runners.RunnerNames[implementation],
|
||||
values: []kv{
|
||||
|
|
|
@ -2,14 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/codemicro/adventOfCode/runtime/benchmark"
|
||||
au "github.com/logrusorgru/aurora"
|
||||
"io/ioutil"
|
||||
"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 (
|
||||
|
@ -66,61 +66,59 @@ func run() error {
|
|||
return err
|
||||
}
|
||||
|
||||
runner := runners.Available[selectedImplementation](selectedChallenge.Dir)
|
||||
fmt.Println()
|
||||
|
||||
runner := runners.Available[selectedImplementation](selectedChallenge.Dir)
|
||||
if err := runner.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = runner.Stop()
|
||||
_ = runner.Cleanup()
|
||||
}()
|
||||
|
||||
lookupTable := make(taskLookupTable)
|
||||
|
||||
if args.Visualise {
|
||||
id := "vis"
|
||||
runner.Queue(&runners.Task{
|
||||
r, err := runner.Run(&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))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if err := runTests(runner, challengeInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !args.TestOnly {
|
||||
if err := runMainTasks(runner, challengeInputString); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if cleanupFn != nil {
|
||||
cleanupFn()
|
||||
}
|
||||
}}
|
||||
|
||||
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{}
|
||||
|
|
|
@ -6,18 +6,28 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
au "github.com/logrusorgru/aurora"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const golangInstallation = "go"
|
||||
const (
|
||||
golangInstallation = "go"
|
||||
golangWrapperFilename = "runtime-wrapper.go"
|
||||
golangWrapperExecutableFilename = "runtime-wrapper"
|
||||
)
|
||||
|
||||
type golangRunner struct {
|
||||
dir string
|
||||
tasks []*Task
|
||||
dir string
|
||||
cmd *exec.Cmd
|
||||
wrapperFilepath string
|
||||
executableFilepath string
|
||||
stdin io.WriteCloser
|
||||
}
|
||||
|
||||
func newGolangRunner(dir string) Runner {
|
||||
|
@ -26,26 +36,12 @@ func newGolangRunner(dir string) Runner {
|
|||
}
|
||||
}
|
||||
|
||||
func (g *golangRunner) Queue(task *Task) {
|
||||
g.tasks = append(g.tasks, task)
|
||||
}
|
||||
|
||||
//go:embed interface/go.go
|
||||
var golangInterface []byte
|
||||
|
||||
func (g *golangRunner) Run() (chan ResultOrError, func()) {
|
||||
|
||||
wrapperFilename := "runtime-wrapper.go"
|
||||
wrapperExecutable := "runtime-wrapper"
|
||||
|
||||
wrapperFilepath := filepath.Join(g.dir, wrapperFilename)
|
||||
wrapperExecutableFilepath := filepath.Join(g.dir, wrapperExecutable)
|
||||
|
||||
// generate interaction data
|
||||
taskJSON, err := json.Marshal(g.tasks)
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
}
|
||||
func (g *golangRunner) Start() error {
|
||||
g.wrapperFilepath = filepath.Join(g.dir, golangWrapperFilename)
|
||||
g.executableFilepath = filepath.Join(g.dir, golangWrapperExecutableFilename)
|
||||
|
||||
// determine package import path
|
||||
buildPath := fmt.Sprintf("github.com/codemicro/adventOfCode/challenges/%s/%s", filepath.Base(filepath.Dir(g.dir)), filepath.Base(g.dir))
|
||||
|
@ -60,50 +56,86 @@ func (g *golangRunner) Run() (chan ResultOrError, func()) {
|
|||
ImportPath string
|
||||
}{importPath})
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
return err
|
||||
}
|
||||
wrapperContent = b.Bytes()
|
||||
}
|
||||
|
||||
// save interaction code
|
||||
err = ioutil.WriteFile(wrapperFilepath, wrapperContent, 0644)
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
if err := ioutil.WriteFile(g.wrapperFilepath, wrapperContent, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Print("Compiling...\r")
|
||||
defer fmt.Print("\n\n")
|
||||
|
||||
// compile executable
|
||||
stderrBuffer := new(bytes.Buffer)
|
||||
|
||||
cmd := exec.Command(golangInstallation, "build", "-tags", "runtime", "-o", wrapperExecutableFilepath, buildPath)
|
||||
cmd := exec.Command(golangInstallation, "build", "-tags", "runtime", "-o", g.executableFilepath, buildPath)
|
||||
cmd.Stderr = stderrBuffer
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return makeErrorChan(fmt.Errorf("compilation failed: %s: %s", err, stderrBuffer.String())), nil
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("compilation failed: %s: %s", err, stderrBuffer.String())
|
||||
}
|
||||
|
||||
if !cmd.ProcessState.Success() {
|
||||
return makeErrorChan(errors.New("compilation failed, hence cannot continue")), nil
|
||||
return errors.New("compilation failed, hence cannot continue")
|
||||
}
|
||||
|
||||
absExecPath, err := filepath.Abs(wrapperExecutableFilepath)
|
||||
// now we run!
|
||||
absExecPath, err := filepath.Abs(g.executableFilepath)
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Print("Running... ")
|
||||
|
||||
// run executable
|
||||
cmd = exec.Command(absExecPath)
|
||||
g.cmd = exec.Command(absExecPath)
|
||||
cmd.Dir = g.dir
|
||||
|
||||
cmd.Stdin = bytes.NewReader(append(taskJSON, '\n'))
|
||||
|
||||
return readResultsFromCommand(cmd), func() {
|
||||
// remove leftover files
|
||||
_ = os.Remove(wrapperFilepath)
|
||||
_ = os.Remove(wrapperExecutableFilepath)
|
||||
if stdin, err := setupBuffers(g.cmd); err != nil {
|
||||
return err
|
||||
} else {
|
||||
g.stdin = stdin
|
||||
}
|
||||
|
||||
return g.cmd.Start()
|
||||
}
|
||||
|
||||
func (g *golangRunner) Stop() error {
|
||||
if g.cmd == nil || g.cmd.Process == nil {
|
||||
return nil
|
||||
}
|
||||
return g.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func (g *golangRunner) Cleanup() error {
|
||||
if g.wrapperFilepath != "" {
|
||||
_ = os.Remove(g.wrapperFilepath)
|
||||
}
|
||||
if g.executableFilepath != "" {
|
||||
_ = os.Remove(g.executableFilepath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *golangRunner) Run(task *Task) (*Result, error) {
|
||||
taskJSON, err := json.Marshal(task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _ = g.stdin.Write(append(taskJSON, '\n'))
|
||||
|
||||
res := new(Result)
|
||||
for {
|
||||
inp, err := checkWait(g.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
|
||||
}
|
|
@ -28,17 +28,16 @@ func sendResult(taskID string, ok bool, output string, duration float64) {
|
|||
|
||||
func run() error {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
tasksBytes, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
task := new(runners.Task)
|
||||
taskBytes, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(taskBytes, task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tasks []*runners.Task
|
||||
if err := json.Unmarshal(tasksBytes, &tasks); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, task := range tasks {
|
||||
var run func() (interface{}, error)
|
||||
|
||||
switch task.Part {
|
||||
|
|
|
@ -22,10 +22,12 @@ type
|
|||
input: string
|
||||
output_dir: Option[string]
|
||||
|
||||
let tasksString = readLine(stdin)
|
||||
let tasks = to(parseJson(tasksString), seq[Task])
|
||||
while true:
|
||||
|
||||
let
|
||||
taskString = readLine(stdin)
|
||||
task = to(parseJson(taskString), Task)
|
||||
|
||||
for _, task in tasks:
|
||||
var runProc: proc(): string
|
||||
|
||||
case task.part
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -6,17 +6,27 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
au "github.com/logrusorgru/aurora"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const nimInstallation = "nim"
|
||||
const (
|
||||
nimInstallation = "nim"
|
||||
nimWrapperFilename = "runtimeWrapper.nim"
|
||||
nimWrapperExecutableFilename = "runtimeWrapper"
|
||||
)
|
||||
|
||||
type nimRunner struct {
|
||||
dir string
|
||||
tasks []*Task
|
||||
dir string
|
||||
cmd *exec.Cmd
|
||||
wrapperFilepath string
|
||||
executableFilepath string
|
||||
stdin io.WriteCloser
|
||||
}
|
||||
|
||||
func newNimRunner(dir string) Runner {
|
||||
|
@ -25,63 +35,88 @@ func newNimRunner(dir string) Runner {
|
|||
}
|
||||
}
|
||||
|
||||
func (n *nimRunner) Queue(task *Task) {
|
||||
n.tasks = append(n.tasks, task)
|
||||
}
|
||||
|
||||
//go:embed interface/nim.nim
|
||||
var nimInterface []byte
|
||||
|
||||
func (n *nimRunner) Run() (chan ResultOrError, func()) {
|
||||
|
||||
wrapperExecutable := "runtimeWrapper"
|
||||
wrapperFilename := wrapperExecutable + ".nim"
|
||||
|
||||
executableFilepath := filepath.Join(n.dir, wrapperExecutable)
|
||||
wrapperFilepath := filepath.Join(n.dir, wrapperFilename)
|
||||
|
||||
// generate interaction data
|
||||
taskJSON, err := json.Marshal(n.tasks)
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
}
|
||||
func (n *nimRunner) Start() error {
|
||||
n.wrapperFilepath = filepath.Join(n.dir, nimWrapperFilename)
|
||||
n.executableFilepath = filepath.Join(n.dir, nimWrapperExecutableFilename)
|
||||
|
||||
// save interaction code
|
||||
err = ioutil.WriteFile(wrapperFilepath, nimInterface, 0644)
|
||||
err := ioutil.WriteFile(n.wrapperFilepath, nimInterface, 0644)
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Print("Compiling...\r")
|
||||
defer fmt.Print("\n\n")
|
||||
|
||||
// compile
|
||||
stderrBuffer := new(bytes.Buffer)
|
||||
cmd := exec.Command(nimInstallation, "compile", "-o:"+executableFilepath, "-d:release", wrapperFilepath)
|
||||
cmd := exec.Command(nimInstallation, "compile", "-o:"+n.executableFilepath, "-d:release", n.wrapperFilepath)
|
||||
cmd.Stderr = stderrBuffer
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return makeErrorChan(fmt.Errorf("compilation failed: %s: %s", err, stderrBuffer.String())), nil
|
||||
return fmt.Errorf("compilation failed: %s: %s", err, stderrBuffer.String())
|
||||
}
|
||||
|
||||
if !cmd.ProcessState.Success() {
|
||||
return makeErrorChan(errors.New("compilation failed, hence cannot continue")), nil
|
||||
return errors.New("compilation failed, hence cannot continue")
|
||||
}
|
||||
|
||||
fmt.Print("Running... ")
|
||||
|
||||
absExecPath, err := filepath.Abs(executableFilepath)
|
||||
// now we run!
|
||||
absExecPath, err := filepath.Abs(n.executableFilepath)
|
||||
if err != nil {
|
||||
return makeErrorChan(err), nil
|
||||
return err
|
||||
}
|
||||
|
||||
// run
|
||||
cmd = exec.Command(absExecPath)
|
||||
cmd.Dir = n.dir
|
||||
cmd.Stdin = bytes.NewReader(append(taskJSON, '\n'))
|
||||
n.cmd = exec.Command(absExecPath)
|
||||
n.cmd.Dir = n.dir
|
||||
|
||||
return readResultsFromCommand(cmd), func() {
|
||||
_ = os.Remove(executableFilepath)
|
||||
_ = os.Remove(wrapperFilepath)
|
||||
if stdin, err := setupBuffers(n.cmd); err != nil {
|
||||
return err
|
||||
} else {
|
||||
n.stdin = stdin
|
||||
}
|
||||
|
||||
return n.cmd.Start()
|
||||
}
|
||||
|
||||
func (n *nimRunner) Stop() error {
|
||||
if n.cmd == nil || n.cmd.Process == nil {
|
||||
return nil
|
||||
}
|
||||
return n.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func (n *nimRunner) Cleanup() error {
|
||||
if n.wrapperFilepath != "" {
|
||||
_ = os.Remove(n.wrapperFilepath)
|
||||
}
|
||||
if n.executableFilepath != "" {
|
||||
_ = os.Remove(n.executableFilepath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nimRunner) Run(task *Task) (*Result, error) {
|
||||
taskJSON, err := json.Marshal(task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _ = n.stdin.Write(append(taskJSON, '\n'))
|
||||
|
||||
res := new(Result)
|
||||
for {
|
||||
inp, err := checkWait(n.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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -9,8 +9,10 @@ const (
|
|||
)
|
||||
|
||||
type Runner interface {
|
||||
Queue(task *Task)
|
||||
Run() (chan ResultOrError, func())
|
||||
Start() error
|
||||
Stop() error
|
||||
Cleanup() error
|
||||
Run(task *Task) (*Result, error)
|
||||
}
|
||||
|
||||
type ResultOrError struct {
|
||||
|
|
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