Merge branch 'feat/queueless'

This commit is contained in:
akp 2021-12-11 00:29:36 +00:00
commit a1d2136fad
No known key found for this signature in database
GPG key ID: AA5726202C8879B7
12 changed files with 477 additions and 266 deletions

View 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)

View file

@ -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{

View file

@ -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
}

View file

@ -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{}

View file

@ -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
}

View file

@ -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 {

View file

@ -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

View file

@ -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"]

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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)))
}
}