From 2af7117891cdb67758219a7075f39c05dc02f3f5 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Thu, 2 Sep 2021 03:14:47 -0500 Subject: Make adapters and internals complient with new directory structure --- fs.go | 115 ++++-------------------------------------------------------------- 1 file changed, 6 insertions(+), 109 deletions(-) (limited to 'fs.go') diff --git a/fs.go b/fs.go index 86de16b..ffc5f05 100644 --- a/fs.go +++ b/fs.go @@ -45,115 +45,6 @@ func directoryExists(path string) bool { return info.IsDir() } -// Find the configuration directory -// Uses: -// 1. PlANR_DIRECTORY env if set -// 2. planr -// 3. .planr -func ConfigDir() string { - - // Return environmental override if set - if dir, isSet := os.LookupEnv("PLANR_DIRECTORY"); isSet { - - if !directoryExists(dir) { - log.Fatalf("Cannot find planr directory %s", dir); - } - - return dir; - } - - cwd, err := os.Getwd() - - if err != nil { - log.Fatal(err) - } - - var rubricDir string - - rubric_search_dirs := [2]string{ - "planr", - ".planr", - } - - found := traverseUp(cwd, func (path string) bool { - - for _, dir := range rubric_search_dirs { - rubricDir = filepath.Join(path, dir) - - if directoryExists(rubricDir) { - return true - } - } - - return false - }); - - if !found { - log.Fatal("Could not find planr directory"); - } - - return rubricDir -} - -func JoinConfigDir(path_ string, file string) string { - if path.IsAbs(path_) { - return path.Join(path_, file) - } - - return path.Join(ConfigDir(), path_, file) -} - -func RootDir() string { - return path.Join(ConfigDir(), "..") -} - -// Find rubric directory at PLANR_DIR/rubric -func RubricDir() string { - rubricDir := path.Join(ConfigDir(), "rubric"); - - if !directoryExists(rubricDir) { - log.Fatal("Could not find the rubric directory inside of planr") - } - - return rubricDir -} - -func BuildDir() string { - buildDir := path.Join(ConfigDir(), "build") - - if !directoryExists(buildDir) { - err := os.Mkdir(buildDir, 0755) - - if err != nil { - log.Fatalf("Cannot create build directory %v\n", err) - } - } - - return buildDir -} - -func CleanBuildDir() { - buildDir := path.Join(ConfigDir(), "build") - if err := os.RemoveAll(buildDir); err != nil { - log.Fatalf("Cannot clean (removeAll) in build directory %v\n", err) - } -} - -func (ac AdapterConfig) Dir() string { - dir := BuildDir() - dir = path.Join(dir, ac.Name) - - if !directoryExists(dir) { - err := os.Mkdir(dir, 0755) - - if err != nil { - log.Fatalf("Cannot create build/%s directory %v\n", ac.Name, err) - } - } - - return dir -} - func basename(path string) string { ext := filepath.Ext(path) return path[0:len(path) - len(ext)] @@ -262,3 +153,9 @@ func collectFromDir( } } } + +func safeCd(newWd string) { + if err := os.Chdir(newWd); err != nil { + log.Fatalf("Could not change into directory %v\n", err) + } +} -- cgit v1.2.3 From adccaeb36de036904c735ca5face1c929a4ba0b2 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Fri, 3 Sep 2021 00:51:47 -0500 Subject: BUG: Fix decoding when no defaults is present --- adapters/gtest/config.go | 11 +++++++++++ fs.go | 4 +++- stddirs.go | 11 ++++------- 3 files changed, 18 insertions(+), 8 deletions(-) (limited to 'fs.go') diff --git a/adapters/gtest/config.go b/adapters/gtest/config.go index 173809d..ba7efb1 100644 --- a/adapters/gtest/config.go +++ b/adapters/gtest/config.go @@ -8,11 +8,16 @@ import ( "path" ) +const ( + DEFAULT_TIMEOUT = 1000 +) + type GtestDefaults struct { Name *string Suite *string Testfile *string Srcs *[]string + Timeout *uint } func (child *GtestDefaults) Inherit(p interface{}) { @@ -22,6 +27,7 @@ func (child *GtestDefaults) Inherit(p interface{}) { if(child.Suite == nil) { child.Suite = parent.Suite } if(child.Testfile == nil) { child.Testfile = parent.Testfile } if(child.Srcs == nil) { child.Srcs = parent.Srcs } + if(child.Timeout == nil) { child.Timeout = parent.Timeout } } @@ -37,6 +43,11 @@ func (g GtestConfig) ensureSatisfied(path string) { } else if g.Testfile == nil { log.Fatalf("\"testfile\" is not defined for unit: %s\n", path) } + + if g.Timeout == nil { + g.Timeout = new(uint) + *g.Timeout = DEFAULT_TIMEOUT; + } } func (cfg GtestConfig) srcList(srcDir string) string { diff --git a/fs.go b/fs.go index ffc5f05..575517c 100644 --- a/fs.go +++ b/fs.go @@ -141,7 +141,9 @@ func collectFromDir( log.Fatalf("Error encountered in %s: %v", child, config) } - config.Inherit(*defaults) + if defaults != nil { + config.Inherit(*defaults) + } tc := TestCase { Path: child, diff --git a/stddirs.go b/stddirs.go index 2385529..e6b510d 100644 --- a/stddirs.go +++ b/stddirs.go @@ -21,13 +21,13 @@ import ( // Env overrides const ( - ENV_CONFIG_DIR="PLANR_PLANR_DIR" - ENV_SRC_DIR="ENV_SRC_DIR" + ENV_CONFIG_DIR="PLANR_CONFIG_DIR" + ENV_SRC_DIR="PLANR_SRC_DIR" ENV_BUILD_DIR="PLANR_BUILD_DIR" ) // Try these search directories -var CONFIG_SEARCH_DIRS = [2] string { +var CONFIG_SEARCH_DIRS = [] string { "planr", ".planr", } @@ -88,7 +88,7 @@ func (c *DirConfig) SetConfigFromTree(cdir string) { for _, dir := range CONFIG_SEARCH_DIRS { configDir = filepath.Join(path, dir) - + if directoryExists(configDir) { return true } @@ -157,9 +157,6 @@ func (c DirConfig) CleanBuild() { log.Fatalf("Cannot build directory %v\n", err) } - // if err := os.Remove(build); err != nil { - // log.Fatalf("Could not remove build directory %v\n", err) - // } } func (c DirConfig) MkBuild() { -- cgit v1.2.3 From d4f9c927e0efad402e1dbfded1a850fb9e0030e6 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Sun, 5 Sep 2021 00:37:23 -0500 Subject: Refactor gtest adapter to fit new pipeline --- adapters.go | 2 +- adapters/gtest/adapter.go | 187 +++++-------------------------------------- adapters/gtest/config.go | 58 +++++++++----- adapters/gtest/executable.go | 186 ++++++++++++++++++++++++++++++++++++++++++ adapters/gtest/templating.go | 14 ++-- cmd/planr/sub/cli.go | 46 +++++------ cmd/planr/sub/evaluate.go | 10 +-- fs.go | 4 +- runner.go | 4 +- testcase.go | 7 +- 10 files changed, 289 insertions(+), 229 deletions(-) create mode 100644 adapters/gtest/executable.go (limited to 'fs.go') diff --git a/adapters.go b/adapters.go index b7c4b27..f6c48cb 100644 --- a/adapters.go +++ b/adapters.go @@ -17,7 +17,7 @@ type Adapter interface { Build(testCase []TestCase) // Called every time source changes - Evaluate(testCase []TestCase) []TestCase + Evaluate(testCase []TestCase) []TestResult } // A parser function takes a blob of TOML and decodes it into diff --git a/adapters/gtest/adapter.go b/adapters/gtest/adapter.go index c727805..dfb035c 100644 --- a/adapters/gtest/adapter.go +++ b/adapters/gtest/adapter.go @@ -1,34 +1,14 @@ package gtest import ( - "context" - "errors" - "fmt" - "io/ioutil" "log" "os" - "os/exec" "path" - "sync" - "time" "golang.furkistan.com/planr" ) const GTEST_CMAKE = "CMakeLists.txt" -func makeUnit(tc planr.TestCase, dirs planr.DirConfig) cmakeUnit { - cfg := tc.AdapterConfig().(*Config) - - testpath := path.Join(dirs.Tests(), *cfg.Testfile) - srclist := cfg.srcList(dirs.Src()) - - return cmakeUnit { - tc.Cname, - testpath, - srclist, - }; -} - func safeWd() string{ wd, err := os.Getwd() @@ -39,104 +19,6 @@ func safeWd() string{ return wd } -type ResultFromId map[string] Result - -func (adapter *Adapter) execTests(cnames []string) ResultFromId { - buildDir := safeWd() - - lut := make(ResultFromId, 0) - for _, exe := range cnames { - - exePath := path.Join(buildDir, exe) - - f, err := ioutil.TempFile(buildDir, "gtest_adapter_*.json") - - if err != nil { - log.Fatal(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 9999*time.Millisecond) - cmd := exec.CommandContext(ctx, exePath, "--gtest_output=json:" + f.Name()) - - defer cancel() - defer os.Remove(f.Name()) - - out, err := cmd.CombinedOutput() - if err != nil { - var exiterr *exec.ExitError - - if !errors.As(err, &exiterr) { - log.Printf("%v\n", err) - os.Exit(exiterr.ExitCode()) - } - } - - results, err := decodeResults(f) - - if err != nil { - log.Printf("Could not collect results from %s: %v", exe, err) - continue - } - - for _, r := range results { - r.testOutput = string(out) - lut[exe + "." + r.id] = r - } - } - - return lut -} - -// An executable may contain more than one test -// Gather all executables and deduplicate them -func exes(tcs []planr.TestCase) []string { - set := make(map[string] bool, 0) - - for _, tc := range tcs { - // Tests which have encountered a failure - // may not have an executable - if tc.Result.Status != planr.PASSING { - continue - } - - if(!set[tc.Cname]) { - set[tc.Cname] = true - } - } - - exes := make([]string, 0) - for k := range set { - exes = append(exes, k) - } - - return exes -} - -func id(tc planr.TestCase) string { - cfg := tc.AdapterConfig().(*Config) - return tc.Cname + "." + *cfg.Suite + "." + *cfg.Name -} - -func compile(wg * sync.WaitGroup, tc * planr.TestCase) { - defer wg.Done() - - cmd := exec.Command("make", tc.Cname) - out, err := cmd.CombinedOutput() - tc.Result = new(planr.TestResult) - - // Don't treat command failure as anything but a build failure - if err != nil{ - var exiterr *exec.ExitError - if errors.As(err, &exiterr) && exiterr.ExitCode() == 0 { - log.Fatal(err) - } - - tc.Result.Status = planr.COMPILATION_FAILURE - } - - tc.Result.DebugOutput = string(out) -} - type Adapter struct { dirs planr.DirConfig } @@ -155,67 +37,42 @@ func (a *Adapter) Init(dirs planr.DirConfig) { func (adapter Adapter) Build(tcs []planr.TestCase) { buildDir := safeWd() - cmakeFile := path.Join(buildDir, GTEST_CMAKE) - units := make([]cmakeUnit, 0) - for _, tc := range tcs { - - cfg := tc.AdapterConfig().(*Config) - cfg.ensureSatisfied(tc.Path) + finalizeConfigs(tcs) - units = append(units, makeUnit(tc, adapter.dirs)) - } + exes := createExecutables(tcs) - genCmake(cmakeFile, units) + cmakeFile := path.Join(buildDir, GTEST_CMAKE) + cmakeUnits := cmakeUnits(exes, adapter.dirs) + + generateCmakeScript(cmakeFile, cmakeUnits) planr.RunCmd("cmake", "-S", ".", "-B", ".") } -// ./planr eval 0.93s user 0.16s system 100% cpu 1.089 total -func (adapter *Adapter) Evaluate(tcs []planr.TestCase) [] planr.TestCase { - var wg sync.WaitGroup - for i := range tcs { - tc := &tcs[i] - wg.Add(1) - go compile(&wg, tc) - } - wg.Wait() - - files := exes(tcs) - resultById := adapter.execTests(files) - - for i := range tcs { - tc := &tcs[i] - result, ok := resultById[id(*tc)] - - // compilation failure - if !ok { - - if tc.Result.Status == planr.PASSING { - cfg := tc.AdapterConfig().(*Config) +func (adapter *Adapter) Evaluate(tcs []planr.TestCase) [] planr.TestResult { + buildDir := safeWd() - log.Printf( - "Could not find testcase %s with name=\"%s\" and suite=\"%s\". Does such a test exist in the test source?", - tc.Cname, - *cfg.Name, - *cfg.Suite, - ) + finalizeConfigs(tcs) + + results := make([]planr.TestResult, 0) + + exes := createExecutables(tcs) - tc.Result.Status = planr.COMPILATION_FAILURE - tc.Result.DebugOutput += fmt.Sprintf("planr: Did not find testcase %s in any test executable\n", id(*tc)) - } + for i := range exes { + succeed, buildFailures := exes[i].compile(buildDir) + if ! succeed { + results = append(results, buildFailures...) continue } - - if !result.pass { - tc.Result.Status = planr.RUNTIME_FAILURE - } - tc.Result.TestOutput = result.testOutput - } + runtimeResults := exes[i].execute(buildDir) - return tcs + results = append(results, runtimeResults...) + } + + return results } func NewAdapter() *Adapter { diff --git a/adapters/gtest/config.go b/adapters/gtest/config.go index 8057d94..4cd2030 100644 --- a/adapters/gtest/config.go +++ b/adapters/gtest/config.go @@ -2,10 +2,10 @@ package gtest import ( "log" - "golang.furkistan.com/planr" "strings" - "github.com/BurntSushi/toml" "path" + "golang.furkistan.com/planr" + "github.com/BurntSushi/toml" ) const ( @@ -16,7 +16,7 @@ type Defaults struct { Name *string Suite *string Testfile *string - Srcs *[]string + Srcs []string Timeout *uint } @@ -26,7 +26,7 @@ func (child *Defaults) Inherit(p interface{}) { if(child.Name == nil) { child.Name = parent.Name } if(child.Suite == nil) { child.Suite = parent.Suite } if(child.Testfile == nil) { child.Testfile = parent.Testfile } - if(child.Srcs == nil) { child.Srcs = parent.Srcs } + if(len(child.Srcs) == 0) { child.Srcs = parent.Srcs } if(child.Timeout == nil) { child.Timeout = parent.Timeout } } @@ -35,34 +35,52 @@ type Config struct { Defaults } -func (g Config) ensureSatisfied(path string) { - if g.Name == nil { +func (c * Config) finalize(path string) { + if c.Name == nil { log.Fatalf("\"name\" is not defined for unit: %s\n", path) - } else if g.Suite == nil { + } else if c.Suite == nil { log.Fatalf("\"suite\" is not defined for unit: %s\n", path) - } else if g.Testfile == nil { + } else if c.Testfile == nil { log.Fatalf("\"testfile\" is not defined for unit: %s\n", path) } - if g.Timeout == nil { - g.Timeout = new(uint) - *g.Timeout = DEFAULT_TIMEOUT; + if c.Timeout == nil { + c.Timeout = new(uint) + *c.Timeout = DEFAULT_TIMEOUT; } } -func (cfg Config) srcList(srcDir string) string { - var srcList string +func srcList(srcdir string, srcs []string) string { + builder := strings.Builder {} - if cfg.Srcs != nil { - srcs := make([]string, len(*cfg.Srcs)) - for i, src := range *cfg.Srcs { - srcs[i] = "\"" + path.Join(srcDir, src) + "\"" - } + for _, src := range srcs { + builder.WriteString("\"") + builder.WriteString(path.Join(srcdir, src)) + builder.WriteString("\"\n ") + } + + return builder.String() +} + +func cmakeUnits(e []executable, dirs planr.DirConfig) []cmakeUnit { + + units := make([]cmakeUnit, len(e)) + for i, exe := range e { + testpath := path.Join(dirs.Tests(), exe.testpath) + srclist := srcList(dirs.Src(), exe.srcs) - srcList = strings.Join(srcs, "\n ") + units[i] = cmakeUnit { exe.exeNm, testpath, srclist } } - return srcList + return units +} + +func finalizeConfigs(tcs []planr.TestCase) { + for i := range tcs { + cfg := tcs[i].AdapterConfig().(*Config) + + cfg.finalize(tcs[i].Path) + } } func ParseConfig(prim toml.Primitive) (planr.InheritableConfig, error) { diff --git a/adapters/gtest/executable.go b/adapters/gtest/executable.go new file mode 100644 index 0000000..b8f79ec --- /dev/null +++ b/adapters/gtest/executable.go @@ -0,0 +1,186 @@ +package gtest + +import ( + "os" + "errors" + "time" + "io/ioutil" + "log" + "os/exec" + "path" + "reflect" + "sort" + "context" + + "golang.furkistan.com/planr" +) + +type executable struct { + exeNm string + testpath string + srcs []string + tcs []planr.TestCase +} + +func createExecutables(tcs []planr.TestCase) []executable { + exes := make(map[string] executable, 0) + + for _, tc := range tcs { + cfg := tc.AdapterConfig().(*Config) + file := *cfg.Testfile + exe, contained := exes[file] + + // For set comparison + sort.Strings(cfg.Srcs) + + if !contained { + exeTcs := make([]planr.TestCase, 1) + exeTcs[0] = tc + + + exe := executable { + planr.Cname("", file), + file, + cfg.Srcs, + exeTcs, + } + + exes[file] = exe + + continue + } + + // We could create two different executables for each source list + // But, that would be confusing so we're going to disallow it + if !reflect.DeepEqual(exe.srcs, cfg.Srcs) { + log.Fatalf( + "Two test case definitions %s and %s have different lists of sources", + exe.testpath, *cfg.Testfile, + ) + } + + exe.tcs = append(exe.tcs, tc) + + exes[file] = exe + } + + exesList := make([]executable, 0) + + for _, exe := range exes { + exesList = append(exesList, exe) + } + + return exesList +} + +func (exe executable) compile(builddir string) (succeeded bool, buildFailures []planr.TestResult) { + cmd := exec.Command("make", "-C", builddir, exe.exeNm) + out, err := cmd.CombinedOutput() + buildFailures = make([]planr.TestResult, 0) + + outputLog := string(out) + + if err != nil{ + var exiterr *exec.ExitError + if errors.As(err, &exiterr) && exiterr.ExitCode() == 0 { + log.Fatalf("Unrecoverable build failure: %v", err) + } + + for i := range exe.tcs { + res := planr.TestResult {} + res.Tc = exe.tcs[i] + res.DebugOutput = outputLog + res.Status = planr.COMPILATION_FAILURE + + buildFailures = append(buildFailures, res) + } + + succeeded = false + + return + } + + succeeded = true + return +} + +const TMPFILENAME = "gtest_adapter_*.json" + +func runGtest(exe string, tc planr.TestCase, builddir string) planr.TestResult { + result := planr.TestResult {} + result.Tc = tc + + exePath := path.Join(builddir, exe) + cfg := tc.AdapterConfig().(*Config) + + f, err := ioutil.TempFile(builddir, TMPFILENAME) + + if err != nil { + log.Fatal(err) + } + + timeout := time.Duration(*cfg.Timeout) * time.Millisecond + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + + jsonFlag := "--gtest_output=json:" + f.Name() + testFlag := "--gtest_filter=" + *cfg.Suite + "." + *cfg.Name + + cmd := exec.CommandContext(ctx, exePath, jsonFlag, testFlag) + + defer cancel() + defer os.Remove(f.Name()) + + out, err := cmd.CombinedOutput() + if err != nil { + var exiterr *exec.ExitError + + if !errors.As(err, &exiterr) { + log.Printf("%v\n", err) + os.Exit(exiterr.ExitCode()) + } + } + + results, err := decodeResults(f) + + if err != nil { + log.Fatalf("Could not collect results from %s: %v", exe, err) + } + + if len(results) < 1 { + log.Fatalf( + "Could not find testcase %s with name=\"%s\" and suite=\"%s\". Does such a test exist in the test source?", + tc.Cname, + *cfg.Name, + *cfg.Suite, + ) + } + + // TODO: Cleanup -- ZERO TESTS? + if len(results) > 1 { + log.Fatalf("Unexpected number of results") + } + + decodeResult := results[0] + + result.TestOutput = string(out) + + if decodeResult.pass { + result.Status = planr.PASSING + } else { + result.Status = planr.RUNTIME_FAILURE + } + + return result +} + +func (exe executable) execute(builddir string) []planr.TestResult { + results := make([]planr.TestResult, len(exe.tcs)) + + for i := range exe.tcs { + results[i] = runGtest(exe.exeNm, exe.tcs[i], builddir) + } + + return results +} + diff --git a/adapters/gtest/templating.go b/adapters/gtest/templating.go index a9a3b07..57532fa 100644 --- a/adapters/gtest/templating.go +++ b/adapters/gtest/templating.go @@ -8,12 +8,12 @@ import ( ) type cmakeUnit struct { - Cname string + ExeNm string File string Srcs string }; -func genCmake(out string, units []cmakeUnit) { +func generateCmakeScript(out string, units []cmakeUnit) { file, err := os.OpenFile(out, os.O_RDWR | os.O_CREATE, 0644) defer func () { err := file.Close() @@ -33,27 +33,29 @@ func genCmake(out string, units []cmakeUnit) { for _, unit := range units { if err := tmpl.Execute(file, unit); err != nil { - log.Fatalf("Failed to generate unit %s: %v", unit.Cname, err); + log.Fatalf("Failed to generate unit %s: %v", unit.ExeNm, err); } } } +// TODO: Add comments func unitTemplate() *template.Template { tmpl, err := template.New("gtest_unit").Parse(` + add_executable( - "{{.Cname}}" + "{{.ExeNm}}" "{{.File}}" {{.Srcs}} ) target_link_libraries( - "{{.Cname}}" + "{{.ExeNm}}" gtest_main ) gtest_discover_tests( - "{{.Cname}}" + "{{.ExeNm}}" ) `) diff --git a/cmd/planr/sub/cli.go b/cmd/planr/sub/cli.go index 64318c2..8dc837e 100644 --- a/cmd/planr/sub/cli.go +++ b/cmd/planr/sub/cli.go @@ -14,25 +14,23 @@ var ( col_label = color.New(color.FgCyan) ); -func tcTitle(tc planr.TestCase) string { - title := tc.Cname +func tcTitle(tr planr.TestResult) string { + title := tr.Tc.Cname - if tc.Config.Title != nil { - title = *tc.Config.Title + if tr.Tc.Config.Title != nil { + title = *tr.Tc.Config.Title } return title } -func tcStatus(tc planr.TestCase) string { +func tcStatus(tc planr.TestResult) string { status := "SILENT" - if tc.Result != nil { - if tc.Result.Status == planr.PASSING { - status = "PASS" - } else { - status = "FAIL" - } + if tc.Status == planr.PASSING { + status = "PASS" + } else { + status = "FAIL" } return status @@ -59,9 +57,9 @@ func pprintFenced(title, value string) { fmt.Println(fence) } -func tcStatusLine(tc planr.TestCase) { - title := tcTitle(tc) - status := tcStatus(tc) +func tcStatusLine(tr planr.TestResult) { + title := tcTitle(tr) + status := tcStatus(tr) if status == "PASS" { col_pass.Printf("[%s] ", status); @@ -72,8 +70,10 @@ func tcStatusLine(tc planr.TestCase) { col_title.Println(title); } -func tcPprint(tc planr.TestCase) { - tcStatusLine(tc) +func tcPprint(tr planr.TestResult) { + tcStatusLine(tr) + + tc := tr.Tc pprintLabeled("id", tc.Cname) @@ -86,22 +86,20 @@ func tcPprint(tc planr.TestCase) { pprintLabeled("description", *tc.Config.Description) } - res := tc.Result - - if res.Status == planr.COMPILATION_FAILURE { + if tr.Status == planr.COMPILATION_FAILURE { - if res.DebugOutput != "" { + if tr.DebugOutput != "" { fmt.Println() - pprintFenced("compilation output", tc.Result.DebugOutput); + pprintFenced("compilation output", tr.DebugOutput); } else { fmt.Println("WARN: No debug output provided") } - } else if res.Status == planr.RUNTIME_FAILURE { + } else if tr.Status == planr.RUNTIME_FAILURE { - if tc.Result.TestOutput != "" { + if tr.TestOutput != "" { fmt.Println() - pprintFenced("test output", tc.Result.TestOutput); + pprintFenced("test output", tr.TestOutput); } } diff --git a/cmd/planr/sub/evaluate.go b/cmd/planr/sub/evaluate.go index 32e19a6..fe864ad 100644 --- a/cmd/planr/sub/evaluate.go +++ b/cmd/planr/sub/evaluate.go @@ -6,26 +6,26 @@ import ( func Evaluate(runner planr.Runner, params []string) { tcs := runner.CollectCases() - tcs = runner.Evaluate(tcs) + trs := runner.Evaluate(tcs) earned := 0.0 total := 0.0 passed := 0 - for _, tc := range tcs { - cfg := tc.Config + for _, tr := range trs { + cfg := tr.Tc.Config if cfg.Points != nil { points := float64(*cfg.Points) total += points - if tc.Result.Status == planr.PASSING { + if tr.Status == planr.PASSING { earned += points passed++ } } - tcPprint(tc) + tcPprint(tr) } printResults( diff --git a/fs.go b/fs.go index 575517c..a42ff2c 100644 --- a/fs.go +++ b/fs.go @@ -50,7 +50,7 @@ func basename(path string) string { return path[0:len(path) - len(ext)] } -func cname(root string, path string) string { +func Cname(root string, path string) string { rel, err := filepath.Rel(root, path) if err != nil { @@ -76,7 +76,7 @@ func collectUnits(root string, cfgs []AdapterConfig) []TestCase { collectFromDir(root, nil, cfgs, &tcs) for i := range tcs { - tcs[i].Cname = cname(root, tcs[i].Path) + tcs[i].Cname = Cname(root, tcs[i].Path) } return tcs diff --git a/runner.go b/runner.go index f613d44..2d66dc6 100644 --- a/runner.go +++ b/runner.go @@ -108,9 +108,9 @@ func (r Runner) Build(tcs []TestCase) { safeCd(r.dirs.Config()) } -func (r Runner) Evaluate(tcs []TestCase) []TestCase { +func (r Runner) Evaluate(tcs []TestCase) []TestResult { testSets := r.groupByAdapter(tcs) - results := make([]TestCase, 0) + results := make([]TestResult, 0) for _, pair := range testSets { adapter := pair.adapter diff --git a/testcase.go b/testcase.go index 7e0bf17..19f1e58 100644 --- a/testcase.go +++ b/testcase.go @@ -7,7 +7,8 @@ import ( type TestStatus uint const ( - PASSING TestStatus = iota + NOT_RUN TestStatus = iota + PASSING COMPILATION_FAILURE RUNTIME_FAILURE ) @@ -17,6 +18,7 @@ type TestResult struct { Status TestStatus DebugOutput string TestOutput string + Tc TestCase } // Program-wide testcase config @@ -43,9 +45,6 @@ type TestCase struct { Cname string Config TestCaseConfig - - Result *TestResult - } func (tc TestCase) AdapterConfig() InheritableConfig { -- cgit v1.2.3 From db947b801555913179c5e700e8b526166e3582ca Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Sun, 5 Sep 2021 02:41:21 -0500 Subject: Add config w/ version information --- cmd/planr/main.go | 6 ++++-- cmd/planr/sub/build.go | 4 ++-- cmd/planr/sub/common.go | 15 +++++++++++++++ cmd/planr/sub/evaluate.go | 4 +++- config.go | 31 ++++++++++++++++++++++--------- fs.go | 4 ++-- rubric_config.go | 4 ++-- 7 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 cmd/planr/sub/common.go (limited to 'fs.go') diff --git a/cmd/planr/main.go b/cmd/planr/main.go index 779ee58..9db30de 100644 --- a/cmd/planr/main.go +++ b/cmd/planr/main.go @@ -79,6 +79,8 @@ func main() { runner := getConfiguredRunner() + cfg := planr.DecodeConfig(runner.ConfigDir()) + subcommand := flag.Arg(0) subargs := flag.Args()[1:] @@ -86,9 +88,9 @@ func main() { case "version": fmt.Printf("%s\n", planr.VERSION) case "build": - sub.Build(runner, subargs) + sub.Build(runner, subargs, cfg) case "evaluate", "eval": - sub.Evaluate(runner, subargs) + sub.Evaluate(runner, subargs, cfg) case "clean": sub.Clean(runner, subargs) case "config": diff --git a/cmd/planr/sub/build.go b/cmd/planr/sub/build.go index caf7bde..2617a68 100644 --- a/cmd/planr/sub/build.go +++ b/cmd/planr/sub/build.go @@ -4,8 +4,8 @@ import ( "golang.furkistan.com/planr" ) - -func Build(runner planr.Runner, params []string) { +func Build(runner planr.Runner, params []string, cfg planr.Config) { + dieIncompatibleVersion(cfg) tcs := runner.CollectCases() runner.Build(tcs) } diff --git a/cmd/planr/sub/common.go b/cmd/planr/sub/common.go new file mode 100644 index 0000000..7e896c1 --- /dev/null +++ b/cmd/planr/sub/common.go @@ -0,0 +1,15 @@ +package sub + +import ( + "golang.furkistan.com/planr" + "os" + "fmt" +) + +func dieIncompatibleVersion(cfg planr.Config) { + if cfg.IncompatibleWithVersion() { + fmt.Fprintf(os.Stderr, "This version of PlanR (%v) is incompatible with config version %s\n", planr.VERSION, cfg.Version) + fmt.Fprintf(os.Stderr, "Please upgrade to version %s or greater\n", cfg.Version) + os.Exit(1) + } +} diff --git a/cmd/planr/sub/evaluate.go b/cmd/planr/sub/evaluate.go index fe864ad..5719b10 100644 --- a/cmd/planr/sub/evaluate.go +++ b/cmd/planr/sub/evaluate.go @@ -4,7 +4,9 @@ import ( "golang.furkistan.com/planr" ) -func Evaluate(runner planr.Runner, params []string) { +func Evaluate(runner planr.Runner, params []string, cfg planr.Config) { + dieIncompatibleVersion(cfg) + tcs := runner.CollectCases() trs := runner.Evaluate(tcs) diff --git a/config.go b/config.go index d2d32e9..88495e3 100644 --- a/config.go +++ b/config.go @@ -4,19 +4,19 @@ import ( "github.com/BurntSushi/toml" "log" "path" + "strings" ) -type planrConfig struct { - Version string - Project_title string +type Config struct { + Version string } -const PLANR_CONFIG = "config.toml" +const PLANR_CONFIG_FILE = "config.toml" -func decodeConfig(configDir string) planrConfig { - cfg := planrConfig { } +func DecodeConfig(configDir string) Config { + cfg := Config { } - configFile := path.Join(configDir, PLANR_CONFIG) + configFile := path.Join(configDir, PLANR_CONFIG_FILE) if _, err := toml.DecodeFile(configFile, &cfg); err != nil { // TODO: handle missing config @@ -26,6 +26,19 @@ func decodeConfig(configDir string) planrConfig { return cfg } -func (cfg planrConfig) isIncompatibleWithVersion() bool { - return cfg.Version > VERSION +func (cfg Config) IncompatibleWithVersion() bool { + if strings.Count(cfg.Version, ".") != 2 { + log.Fatalf("Version %s is not semantic", cfg.Version) + } + + cfgbits := strings.SplitN(cfg.Version, ".", 2) + bits := strings.SplitN(VERSION, ".", 2) + + // major version change + if cfgbits[0] != bits[0] { + return true + } + + // Config newer, possible feature additions + return cfgbits[1] > bits[1] } diff --git a/fs.go b/fs.go index a42ff2c..04a3522 100644 --- a/fs.go +++ b/fs.go @@ -99,7 +99,7 @@ func collectFromDir( // Process defaults for this directory if a defaults.toml is found defaultsPath := path.Join(dir, DEFAULTS) if info, err := os.Stat(defaultsPath); err == nil && !info.IsDir() { - d, err := DecodeDefaults(defaultsPath, cfgs) + d, err := DecodeRubricDefaults(defaultsPath, cfgs) if err != nil { log.Fatalf("Error encounter in %s: %v\n", defaultsPath, err); @@ -135,7 +135,7 @@ func collectFromDir( } // Decode a unit - config, err := DecodeConfig(child, cfgs) + config, err := DecodeRubricConfig(child, cfgs) if err != nil { log.Fatalf("Error encountered in %s: %v", child, config) diff --git a/rubric_config.go b/rubric_config.go index 887bbb0..322e58a 100644 --- a/rubric_config.go +++ b/rubric_config.go @@ -135,7 +135,7 @@ func (defaults *Defaults) decodeAdapters( } // Decode defaults.toml -func DecodeDefaults(path string, adapterCfg []AdapterConfig) (Defaults, error) { +func DecodeRubricDefaults(path string, adapterCfg []AdapterConfig) (Defaults, error) { defaults := Defaults { } if _, err := toml.DecodeFile(path, &defaults); err != nil { @@ -151,7 +151,7 @@ func DecodeDefaults(path string, adapterCfg []AdapterConfig) (Defaults, error) { } // Decode an individual unit -func DecodeConfig(path string, adapterCfg []AdapterConfig) (TestCaseConfig, error) { +func DecodeRubricConfig(path string, adapterCfg []AdapterConfig) (TestCaseConfig, error) { config := TestCaseConfig { } if _, err := toml.DecodeFile(path, &config); err != nil { -- cgit v1.2.3 From 243c7db4b094fdb25c54d0017712a655da138eef Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Sun, 5 Sep 2021 13:38:27 -0500 Subject: Preserve original ordering --- fs.go | 1 + runner.go | 3 +++ testcase.go | 9 +++++++++ 3 files changed, 13 insertions(+) (limited to 'fs.go') diff --git a/fs.go b/fs.go index 04a3522..b76f182 100644 --- a/fs.go +++ b/fs.go @@ -77,6 +77,7 @@ func collectUnits(root string, cfgs []AdapterConfig) []TestCase { for i := range tcs { tcs[i].Cname = Cname(root, tcs[i].Path) + tcs[i].readIdx = i } return tcs diff --git a/runner.go b/runner.go index 9c1e385..15f08ae 100644 --- a/runner.go +++ b/runner.go @@ -4,6 +4,7 @@ import ( "log" "os" "path" + "sort" ) type Runner struct { @@ -128,6 +129,8 @@ func (r Runner) Evaluate(tcs []TestCase) []TestResult { for range testSets { results = append(results, (<-c)...) } + + sort.Sort(ByReadIdx(results)) safeCd(r.dirs.Config()) diff --git a/testcase.go b/testcase.go index 19f1e58..d1db292 100644 --- a/testcase.go +++ b/testcase.go @@ -45,8 +45,17 @@ type TestCase struct { Cname string Config TestCaseConfig + + // Reorder according to original read order after concurrent operation + readIdx int } func (tc TestCase) AdapterConfig() InheritableConfig { return tc.Config.adapters_[*tc.Config.Adapter] } + +type ByReadIdx []TestResult + +func (a ByReadIdx) Len() int { return len(a) } +func (a ByReadIdx) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByReadIdx) Less(i, j int) bool { return a[i].Tc.readIdx < a[j].Tc.readIdx } -- cgit v1.2.3