summaryrefslogtreecommitdiff
path: root/adapters/gtest
diff options
context:
space:
mode:
Diffstat (limited to 'adapters/gtest')
-rw-r--r--adapters/gtest/adapter.go187
-rw-r--r--adapters/gtest/config.go58
-rw-r--r--adapters/gtest/executable.go186
-rw-r--r--adapters/gtest/templating.go14
4 files changed, 254 insertions, 191 deletions
diff --git a/adapters/gtest/adapter.go b/adapters/gtest/adapter.go
index 7033c7f..ec11748 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.flu0r1ne.net/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 3305977..04d426c 100644
--- a/adapters/gtest/config.go
+++ b/adapters/gtest/config.go
@@ -2,10 +2,10 @@ package gtest
import (
"log"
- "golang.flu0r1ne.net/planr"
"strings"
- "github.com/BurntSushi/toml"
"path"
+ "golang.flu0r1ne.net/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..78b0b56
--- /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.flu0r1ne.net/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}}"
)
`)