aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--adapters.go22
-rw-r--r--adapters/gtest/adapter.go106
-rw-r--r--adapters/gtest/config.go (renamed from adapters/gtest_adapter.go)120
-rw-r--r--adapters/gtest/results.go75
-rw-r--r--adapters/gtest/templating.go84
-rw-r--r--adapters/template_defs.go50
-rw-r--r--fs.go3
-rw-r--r--runner.go59
8 files changed, 317 insertions, 202 deletions
diff --git a/adapters.go b/adapters.go
index 7c7dd0b..8419c8b 100644
--- a/adapters.go
+++ b/adapters.go
@@ -5,24 +5,12 @@ package planr
// Test cases matching adapter configurations will be
// fed into the adapter interface
type Adapter interface {
-
- /* CONFIGURATION HOOKS */
-
+ //
Config() AdapterConfig
- /* BUILD CYCLE */
+ // Called once to preform expensive code generation
+ Build(testCase []*TestCase)
- // Called once at the beginning of the build process
- InitializeBuild()
- // Called once with every registered test case
- // Can access configuration directly
- Build(testCase TestCase)
- // Called once after all builds
- FinalizeBuild()
- // Called pre-evaluate
- Make()
- // Called once per test case after FinalizeBuild
- Evaluate(testCase TestCase) TestResult
- // Called once after each test has been evaluated
- Cleanup()
+ // Called every time source changes
+ Evaluate(testCase []*TestCase)
}
diff --git a/adapters/gtest/adapter.go b/adapters/gtest/adapter.go
new file mode 100644
index 0000000..09e6ac1
--- /dev/null
+++ b/adapters/gtest/adapter.go
@@ -0,0 +1,106 @@
+package gtest
+
+import (
+ "context"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "time"
+ "path"
+ "errors"
+ "golang.flu0r1ne.net/planr"
+)
+
+const GTEST_CMAKE = "CMakeLists.txt"
+
+func mkUnit(tc *planr.TestCase) cmakeUnit {
+ cfg := tc.AdapterConfig("gtest").(*GtestConfig)
+
+ return cmakeUnit {
+ tc.Cname,
+ cfg.joinTests(*cfg.Testfile),
+ cfg.srcList(),
+ };
+}
+
+func chdir(dir string) {
+ if err := os.Chdir(dir); err != nil {
+ log.Fatal(err)
+ }
+}
+
+type GtestAdapter struct {}
+
+func (a *GtestAdapter) Config() planr.AdapterConfig {
+ return planr.AdapterConfig {
+ Name: "gtest",
+ ParseConfig: ParseConfig,
+ ParseDefaultConfig: ParseDefaultConfig,
+ }
+}
+
+func (adapter *GtestAdapter) Build(tcs []*planr.TestCase) {
+ buildDir := adapter.Config().Dir()
+ cmakeFile := path.Join(buildDir, GTEST_CMAKE)
+
+ units := make([]cmakeUnit, 0)
+ for _, tc := range tcs {
+
+ fmt.Printf("[R] Building %s (%s)\n", tc.Cname, tc.Path)
+ cfg := tc.AdapterConfig("gtest").(*GtestConfig)
+ cfg.ensureSatisfied(tc.Path)
+
+ units = append(units, mkUnit(tc))
+ }
+
+ genCmake(cmakeFile, units)
+
+ chdir(buildDir)
+ planr.RunCmd("cmake", "-S", ".", "-B", ".")
+}
+
+func (adapter *GtestAdapter) Evaluate(tcs []*planr.TestCase) {
+ planr.RunCmd("make", "-k")
+ buildDir := adapter.Config().Dir()
+
+ results := make([]planr.TestResult, 0)
+ for _, tc := range tcs {
+ fmt.Printf("[R] Evaluating %s (%s)\n", tc.Cname, tc.Path)
+
+ exe := path.Join(buildDir, tc.Cname)
+
+ 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, exe, "--gtest_output=json:" + f.Name())
+
+ defer cancel()
+ defer os.Remove(f.Name())
+
+ exitFail := false
+ if err := cmd.Run(); err != nil {
+ var exiterr *exec.ExitError
+
+ if errors.As(err, &exiterr) && exiterr.ExitCode() == 1{
+ exitFail = true
+ } else {
+ log.Printf("%v\n", err)
+ os.Exit(exiterr.ExitCode())
+ }
+ }
+
+ if exitFail {
+ fmt.Printf("Failure detected")
+ }
+
+ results = append(results, decodeResults(f)...)
+ }
+
+ fmt.Println(results)
+}
diff --git a/adapters/gtest_adapter.go b/adapters/gtest/config.go
index fd06a48..6a6c8bf 100644
--- a/adapters/gtest_adapter.go
+++ b/adapters/gtest/config.go
@@ -1,20 +1,12 @@
-package adapters
+package gtest
import (
- "bytes"
- "log"
- "os"
- "path"
- "strings"
- "text/template"
-
- "github.com/BurntSushi/toml"
- "golang.flu0r1ne.net/planr"
+ "log"
+ "golang.flu0r1ne.net/planr"
+ "strings"
+ "github.com/BurntSushi/toml"
)
-/*
- CONFIGURATION
-*/
type GtestDefaults struct {
Name *string
Suite *string
@@ -40,6 +32,15 @@ type GtestConfig struct {
GtestDefaults
}
+func (g GtestConfig) ensureSatisfied(path string) {
+ if g.Name == nil {
+ log.Fatalf("\"name\" is not defined for unit: %s\n", path)
+ } else if g.Suite == nil {
+ log.Fatalf("\"suite\" is not defined for unit: %s\n", path)
+ } else if g.Testfile == nil {
+ log.Fatalf("\"testfile\" is not defined for unit: %s\n", path)
+ }
+}
func (cfg GtestConfig) joinTests(path_ string) string {
if cfg.Test_root == nil {
@@ -72,16 +73,6 @@ func (cfg GtestConfig) srcList() string {
return srcList
}
-func (g GtestConfig) EnsureSatisfied(path string) {
- if g.Name == nil {
- log.Fatalf("\"name\" is not defined for unit: %s\n", path)
- } else if g.Suite == nil {
- log.Fatalf("\"suite\" is not defined for unit: %s\n", path)
- } else if g.Testfile == nil {
- log.Fatalf("\"testfile\" is not defined for unit: %s\n", path)
- }
-}
-
func primitiveDecode(primitive toml.Primitive, config interface{}) {
if err := toml.PrimitiveDecode(primitive, config); err != nil {
log.Fatal(err)
@@ -103,86 +94,3 @@ func ParseDefaultConfig(prim toml.Primitive) planr.InheritableConfig {
return &config
}
-
-/*
- BUILD PROCESS
-*/
-
-type GtestAdapter struct {
- unitTmpl *template.Template
- cbuf bytes.Buffer
-}
-
-func (a *GtestAdapter) Config() planr.AdapterConfig {
- return planr.AdapterConfig {
- Name: "gtest",
- ParseConfig: ParseConfig,
- ParseDefaultConfig: ParseDefaultConfig,
- }
-}
-
-func (a *GtestAdapter) InitializeBuild() {
- a.unitTmpl = UnitTemplate()
- a.cbuf = bytes.Buffer{}
-
- WriteCMakeBoiler(&a.cbuf)
-}
-
-const GTEST_CMAKE = "CMakeLists.txt"
-
-func Chdir(dir string) {
- if err := os.Chdir(dir); err != nil {
- log.Fatal(err)
- }
-}
-
-func (a *GtestAdapter) Build(tc planr.TestCase) {
- cfg := tc.AdapterConfig("gtest").(*GtestConfig)
- cfg.EnsureSatisfied(tc.Path)
-
- cname := tc.Cname
- testfile := cfg.joinTests(*cfg.Testfile)
- srcList := cfg.srcList()
-
- err := a.unitTmpl.Execute(&a.cbuf, struct {Cname, File, Srcs string} {
- cname, testfile, srcList,
- })
-
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func (a *GtestAdapter) FinalizeBuild() {
- dir := a.Config().ConfigDir()
- cmakeFile := path.Join(dir, GTEST_CMAKE)
-
- file, err := os.OpenFile(cmakeFile, os.O_RDWR | os.O_CREATE, 0644)
- defer func () {
- err := file.Close()
-
- if err != nil {
- log.Fatal(err)
- }
- }()
-
- if err != nil {
- log.Fatalf("Could not open CMakeFile (%s)\n%v", cmakeFile, err)
- }
-
- file.Write(a.cbuf.Bytes())
-
- Chdir(dir)
-
- planr.RunCmd("cmake", "-S", ".", "-B", ".")
-}
-
-func (a *GtestAdapter) Make() {
- planr.RunCmd("make", "-k")
-}
-
-func (a *GtestAdapter) Evaluate(tc planr.TestCase) planr.TestResult {
- return planr.TestResult {}
-}
-
-func (a *GtestAdapter) Cleanup() { }
diff --git a/adapters/gtest/results.go b/adapters/gtest/results.go
new file mode 100644
index 0000000..f8c8a23
--- /dev/null
+++ b/adapters/gtest/results.go
@@ -0,0 +1,75 @@
+package gtest
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+ "log"
+ "time"
+
+ "golang.flu0r1ne.net/planr"
+)
+
+type gFailure struct {
+ Failure string `json:"failure"`
+ Type string `json:"type"`
+}
+
+type gTestsuite struct {
+ Name string `json:"name"`
+ Status string `json:"status"`
+ Result string `json:"result"`
+ Timestamp time.Time `json:"timestamp"`
+ Time string `json:"time"`
+ Classname string `json:"classname"`
+ Failures []gFailure `json:"failures"`
+}
+
+type gTestsuites struct {
+ Name string `json:"name"`
+ Tests int `json:"tests"`
+ Failures int `json:"failures"`
+ Disabled int `json:"disabled"`
+ Errors int `json:"errors"`
+ Timestamp time.Time `json:"timestamp"`
+ Time string `json:"time"`
+ Testsuite []gTestsuite `json:"testsuite"`
+}
+
+type gResults struct {
+ Tests int `json:"tests"`
+ Failures int `json:"failures"`
+ Disabled int `json:"disabled"`
+ Errors int `json:"errors"`
+ Timestamp time.Time `json:"timestamp"`
+ Time string `json:"time"`
+ Name string `json:"name"`
+ Testsuites []gTestsuites `json:"testsuites"`
+}
+
+func decodeResults(r io.Reader) []planr.TestResult {
+ var results gResults
+ buf := bytes.Buffer{}
+
+ if _, err := buf.ReadFrom(r); err != nil {
+ log.Fatal(err)
+ }
+
+ if err := json.Unmarshal(buf.Bytes(), &results); err != nil {
+ log.Fatal(err)
+ }
+
+ decoded := make([]planr.TestResult, 0)
+ for _, suite := range results.Testsuites {
+ for _, test := range suite.Testsuite {
+ n := len(test.Failures)
+
+ decoded = append(decoded, planr.TestResult {
+ Id: suite.Name + "_" + test.Name,
+ Pass: n == 0,
+ })
+ }
+ }
+
+ return decoded
+}
diff --git a/adapters/gtest/templating.go b/adapters/gtest/templating.go
new file mode 100644
index 0000000..a78eaf8
--- /dev/null
+++ b/adapters/gtest/templating.go
@@ -0,0 +1,84 @@
+package gtest
+
+import (
+ "io"
+ "log"
+ "text/template"
+ "os"
+)
+
+type cmakeUnit struct {
+ Cname string
+ File string
+ Srcs string
+};
+
+func genCmake(out string, units []cmakeUnit) {
+ file, err := os.OpenFile(out, os.O_RDWR | os.O_CREATE, 0644)
+ defer func () {
+ err := file.Close()
+
+ if err != nil {
+ log.Fatal(err)
+ }
+ }()
+
+ if err != nil {
+ log.Fatalf("Could not open CMakeFile (%s)\n%v", out, err)
+ }
+
+ writeBoiler(file)
+
+ tmpl := unitTemplate()
+
+ for _, unit := range units {
+ if err := tmpl.Execute(file, unit); err != nil {
+ log.Fatalf("Failed to generate unit %s: %v", unit.Cname, err);
+ }
+ }
+}
+
+
+func unitTemplate() *template.Template {
+ tmpl, err := template.New("gtest_unit").Parse(`
+add_executable(
+ {{.Cname}}
+ {{.File}}
+ {{.Srcs}}
+)
+
+target_link_libraries(
+ {{.Cname}}
+ gtest_main
+)
+
+gtest_discover_tests(
+ {{.Cname}}
+)
+`)
+
+ if err != nil {
+ log.Fatalf("Cannot load Gtest Unit Template %v", err)
+ }
+
+ return tmpl
+}
+
+func writeBoiler(w io.Writer) {
+ w.Write([]byte(`
+cmake_minimum_required (VERSION 3.1.0)
+
+project(PlanRGtestAdapter)
+
+include(FetchContent)
+FetchContent_Declare(
+ googletest
+ URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
+)
+
+include(GoogleTest)
+FetchContent_MakeAvailable(googletest)
+`))
+}
+
+
diff --git a/adapters/template_defs.go b/adapters/template_defs.go
deleted file mode 100644
index 54f2840..0000000
--- a/adapters/template_defs.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package adapters
-
-import (
- "io"
- "log"
- "text/template"
-)
-
-
-func UnitTemplate() *template.Template {
- tmpl, err := template.New("gtest_unit").Parse(`
-add_executable(
- {{.Cname}}
- {{.File}}
- {{.Srcs}}
-)
-
-target_link_libraries(
- {{.Cname}}
- gtest_main
-)
-
-gtest_discover_tests(
- {{.Cname}}
-)
-`)
-
- if err != nil {
- log.Fatalf("Cannot load Gtest Unit Template %v", err)
- }
-
- return tmpl
-}
-
-func WriteCMakeBoiler(w io.Writer) {
- w.Write([]byte(`
-cmake_minimum_required (VERSION 3.1.0)
-
-project(PlanRGtestAdapter)
-
-include(FetchContent)
-FetchContent_Declare(
- googletest
- URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
-)
-
-include(GoogleTest)
-FetchContent_MakeAvailable(googletest)
-`))
-}
diff --git a/fs.go b/fs.go
index 978c291..bd27cf8 100644
--- a/fs.go
+++ b/fs.go
@@ -103,7 +103,6 @@ func JoinConfigDir(path_ string, file string) string {
return path.Join(ConfigDir(), path_, file)
}
-
func RootDir() string {
return path.Join(ConfigDir(), "..")
}
@@ -140,7 +139,7 @@ func CleanBuildDir() {
}
}
-func (ac AdapterConfig) ConfigDir() string {
+func (ac AdapterConfig) Dir() string {
dir := BuildDir()
dir = path.Join(dir, ac.Name)
diff --git a/runner.go b/runner.go
index 1470fbd..5b76cf1 100644
--- a/runner.go
+++ b/runner.go
@@ -1,8 +1,6 @@
package planr
-import (
- "fmt"
-)
+import "fmt"
type Runner struct {
adapters []Adapter
@@ -22,45 +20,52 @@ func (r Runner) adapterCfgs() []AdapterConfig {
return cgs
}
-// [Initialization] -> [Generation] -> [Finalization] ->
-// [Build] -> [Evaluation] -> [Clean]
+type TcTab map[string] []*TestCase
-func (r Runner) cycle(tcs []TestCase) []TestResult {
- results := make([]TestResult, 0)
+func (r Runner) buildTcLUT(tcs []TestCase) TcTab {
+ m := make(TcTab, 0)
- for _, adapter := range r.adapters {
- aname := adapter.Config().Name
-
- adapter.InitializeBuild()
-
- for _, tc := range tcs {
- if tc.ContainsAdapter(aname) {
- fmt.Printf("[R] Building %s\n", tc.Path)
- adapter.Build(tc)
- }
+ for i := range tcs {
+ tc := &tcs[i]
+ for nm := range tc.Config.adapters_ {
+ m[nm] = append(m[nm], tc)
}
+ }
- adapter.FinalizeBuild()
+ return m
+}
- adapter.Make()
+func (r Runner) Build(tcs []TestCase) {
+ tcTab := r.buildTcLUT(tcs)
- for _, tc := range tcs {
- if tc.ContainsAdapter(aname) {
- fmt.Printf("[R] Evaluating %s\n", tc.Path)
- results = append(results, adapter.Evaluate(tc))
- }
- }
+ for _, adapter := range r.adapters {
+ nm := adapter.Config().Name
+
+ fmt.Printf("[R] Building adapter \"%s\"\n", nm)
+ adapter.Build(tcTab[nm])
+ }
+}
- adapter.Cleanup()
+func (r Runner) Evaluate(tcs []TestCase) []TestResult {
+ tcTab := r.buildTcLUT(tcs)
+ results := make([]TestResult, 0)
+
+ for _, adapter := range r.adapters {
+ nm := adapter.Config().Name
+
+ fmt.Printf("[R] Evaluating adapter \"%s\"\n", nm)
+ adapter.Evaluate(tcTab[nm])
}
return results
}
+
func (r Runner) Run(root string) [] TestResult {
tcs := collectUnits(root, r.adapterCfgs())
- trs := r.cycle(tcs)
+ r.Build(tcs)
+ trs := r.Evaluate(tcs)
return trs
}