summaryrefslogtreecommitdiff
path: root/adapters/gtest
diff options
context:
space:
mode:
authorflu0r1ne <flu0r1ne@flu0r1ne.net>2021-09-05 20:37:19 -0500
committerflu0r1ne <flu0r1ne@flu0r1ne.net>2021-09-05 20:37:19 -0500
commitf5b60238e05b124eb40f805eb4a0bbfc0b043da5 (patch)
treef461bff108f5ddafc4078aa7394d7bf2a6309cc9 /adapters/gtest
parent8f22bd4f5b4eb6996c524bcb6948d36cef0ac822 (diff)
parentfd66fb134967067ed7e1c3182325f646b73c730b (diff)
downloaddeb-planr-f5b60238e05b124eb40f805eb4a0bbfc0b043da5.tar.xz
deb-planr-f5b60238e05b124eb40f805eb4a0bbfc0b043da5.zip
Merge branch 'upstream' into ppa
Merge v0.1.0
Diffstat (limited to 'adapters/gtest')
-rw-r--r--adapters/gtest/adapter.go202
-rw-r--r--adapters/gtest/config.go97
-rw-r--r--adapters/gtest/executable.go185
-rw-r--r--adapters/gtest/templating.go57
4 files changed, 325 insertions, 216 deletions
diff --git a/adapters/gtest/adapter.go b/adapters/gtest/adapter.go
index f4fde27..2961f29 100644
--- a/adapters/gtest/adapter.go
+++ b/adapters/gtest/adapter.go
@@ -1,199 +1,87 @@
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 mkUnit(tc *planr.TestCase) cmakeUnit {
- cfg := tc.AdapterConfig().(*GtestConfig)
+func safeWd() string{
+ wd, err := os.Getwd()
- return cmakeUnit {
- tc.Cname,
- cfg.joinTests(*cfg.Testfile),
- cfg.srcList(),
- };
-}
+ if err != nil {
+ log.Fatalf("Could not get GtestBuildDir %s %v\n", wd, err)
+ }
+ return wd
+}
-type GtestAdapter struct {}
+type Adapter struct {
+ dirs planr.DirConfig
+}
-func (a *GtestAdapter) Config() planr.AdapterConfig {
+func (a *Adapter) 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 {
-
- cfg := tc.AdapterConfig().(*GtestConfig)
- cfg.ensureSatisfied(tc.Path)
-
- units = append(units, mkUnit(tc))
}
-
- genCmake(cmakeFile, units)
-
- planr.RunCmd("cmake", "-S", ".", "-B", ".")
}
-type ResultFromId map[string] Result
-
-func (adapter *GtestAdapter) execTests(cnames []string) ResultFromId {
- buildDir := adapter.Config().Dir()
-
- 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())
+func (a *Adapter) Init(dirs planr.DirConfig) {
+ a.dirs = dirs
+}
- out, err := cmd.CombinedOutput()
- if err != nil {
- var exiterr *exec.ExitError
+func (adapter Adapter) Build(tcs []planr.TestCase) {
+ buildDir := safeWd()
- if !errors.As(err, &exiterr) {
- log.Printf("%v\n", err)
- os.Exit(exiterr.ExitCode())
- }
- }
+ finalizeConfigs(tcs)
- results, err := decodeResults(f)
+ exes := createExecutables(tcs)
- if err != nil {
- log.Printf("Could not collect results from %s: %v", exe, err)
- continue
- }
+ cmakeFile := path.Join(buildDir, GTEST_CMAKE)
+ cmakeUnits := cmakeUnits(exes, adapter.dirs)
- for _, r := range results {
- r.testOutput = string(out)
- lut[exe + "." + r.id] = r
- }
- }
+ generateCmakeScript(cmakeFile, cmakeUnits)
- return lut
+ planr.RunCmd("cmake", "-S", ".", "-B", ".")
}
-// 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)
+func (adapter *Adapter) Evaluate(tcs []planr.TestCase) [] planr.TestResult {
+ buildDir := safeWd()
+
+ finalizeConfigs(tcs)
- 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)
- }
+ results := make([]planr.TestResult, 0)
- return exes
-}
-
-func id(tc *planr.TestCase) string {
- cfg := tc.AdapterConfig().(*GtestConfig)
- return tc.Cname + "." + *cfg.Suite + "." + *cfg.Name
-}
+ exes := createExecutables(tcs)
-func compile(wg * sync.WaitGroup, tc *planr.TestCase) {
- defer wg.Done()
+ c := make(chan []planr.TestResult, len(exes))
+ for i := range exes {
+ go func(exe *executable) {
+ succeed, buildFailures := exe.compile(buildDir)
- cmd := exec.Command("make", tc.Cname)
- out, err := cmd.CombinedOutput()
- tc.Result = new(planr.TestResult)
+ if ! succeed {
+ c <- buildFailures
+ return
+ }
- // 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)
- }
+ runtimeResults := exe.execute(buildDir)
- tc.Result.Status = planr.COMPILATION_FAILURE
+ c <- runtimeResults
+ }(&exes[i])
}
- tc.Result.DebugOutput = string(out)
-}
-
-// ./planr eval 0.93s user 0.16s system 100% cpu 1.089 total
-func (adapter *GtestAdapter) Evaluate(tcs []*planr.TestCase) {
- var wg sync.WaitGroup
- for _, tc := range tcs {
- wg.Add(1)
- go compile(&wg, tc)
+ for range exes {
+ results = append(results, (<-c)...)
}
- wg.Wait()
-
- files := exes(tcs)
- resultById := adapter.execTests(files)
-
- for _, tc := range tcs {
- result, ok := resultById[id(tc)]
-
- // compilation failure
- if !ok {
- fmt.Printf("CAN'T FIND %s: status %d\n", tc.Cname, tc.Result.Status)
-
- if tc.Result.Status == planr.PASSING {
- cfg := tc.AdapterConfig().(*GtestConfig)
-
- 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,
- )
-
- tc.Result.Status = planr.COMPILATION_FAILURE
- tc.Result.DebugOutput += fmt.Sprintf("planr: Did not find testcase %s in any test executable\n", id(tc))
- }
-
- continue
- }
- if !result.pass {
- tc.Result.Status = planr.RUNTIME_FAILURE
- }
+ return results
+}
- tc.Result.TestOutput = result.testOutput
- }
+func NewAdapter() *Adapter {
+ return new(Adapter)
}
diff --git a/adapters/gtest/config.go b/adapters/gtest/config.go
index cff45fa..533f266 100644
--- a/adapters/gtest/config.go
+++ b/adapters/gtest/config.go
@@ -2,79 +2,88 @@ package gtest
import (
"log"
- "golang.flu0r1ne.net/planr"
"strings"
+ "path"
+ "golang.flu0r1ne.net/planr"
"github.com/BurntSushi/toml"
)
-type GtestDefaults struct {
- Name *string
- Suite *string
- Testfile *string
- Test_root *string
- Srcs *[]string
- Srcs_root *string
+const (
+ DEFAULT_TIMEOUT = 1000
+)
+
+type Defaults struct {
+ Name string
+ Suite string
+ Testfile string
+ Srcs []string
+ Timeout uint
}
-func (child *GtestDefaults) Inherit(p interface{}) {
- parent := p.(*GtestDefaults)
+func (child *Defaults) Inherit(p interface{}) {
+ parent := p.(*Defaults)
- 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.Test_root == nil) { child.Test_root = parent.Test_root }
- if(child.Srcs == nil) { child.Srcs = parent.Srcs }
- if(child.Srcs_root == nil) { child.Srcs_root = parent.Srcs_root }
+ if(child.Name == "") { child.Name = parent.Name }
+ if(child.Suite == "") { child.Suite = parent.Suite }
+ if(child.Testfile == "") { child.Testfile = parent.Testfile }
+ if(len(child.Srcs) == 0) { child.Srcs = parent.Srcs }
+ if(child.Timeout == 0) { child.Timeout = parent.Timeout }
}
-type GtestConfig struct {
- GtestDefaults
+type Config struct {
+ Defaults
}
-func (g GtestConfig) ensureSatisfied(path string) {
- if g.Name == nil {
+func (c * Config) finalize(path string) {
+ if c.Name == "" {
log.Fatalf("\"name\" is not defined for unit: %s\n", path)
- } else if g.Suite == nil {
+ } else if c.Suite == "" {
log.Fatalf("\"suite\" is not defined for unit: %s\n", path)
- } else if g.Testfile == nil {
+ } else if c.Testfile == "" {
log.Fatalf("\"testfile\" is not defined for unit: %s\n", path)
}
+
+ if c.Timeout == 0 {
+ c.Timeout = DEFAULT_TIMEOUT;
+ }
}
-func (cfg GtestConfig) joinTests(path_ string) string {
- if cfg.Test_root == nil {
- return planr.JoinConfigDir("tests", path_)
+func srcList(srcdir string, srcs []string) string {
+ builder := strings.Builder {}
+
+ for _, src := range srcs {
+ builder.WriteString("\"")
+ builder.WriteString(path.Join(srcdir, src))
+ builder.WriteString("\"\n ")
}
-
- return planr.JoinConfigDir(*cfg.Test_root, path_)
+
+ return builder.String()
}
-func (cfg GtestConfig) joinSrcs(path_ string) string {
- if cfg.Srcs_root == nil {
- return planr.JoinConfigDir("../src", path_)
+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)
+
+ units[i] = cmakeUnit { exe.exeNm, testpath, srclist }
}
- return planr.JoinConfigDir(*cfg.Srcs_root, path_)
+ return units
}
-func (cfg GtestConfig) srcList() string {
- var srcList string
+func finalizeConfigs(tcs []planr.TestCase) {
+ for i := range tcs {
+ cfg := tcs[i].AdapterConfig().(*Config)
- if cfg.Srcs != nil {
- srcs := make([]string, len(*cfg.Srcs))
- for i, src := range *cfg.Srcs {
- srcs[i] = "\"" + cfg.joinSrcs(src) + "\""
- }
-
- srcList = strings.Join(srcs, "\n ")
+ cfg.finalize(tcs[i].Path)
}
-
- return srcList
}
func ParseConfig(prim toml.Primitive) (planr.InheritableConfig, error) {
- config := GtestConfig{}
+ config := Config{}
if err := toml.PrimitiveDecode(prim, &config); err != nil {
return nil, err
@@ -84,7 +93,7 @@ func ParseConfig(prim toml.Primitive) (planr.InheritableConfig, error) {
}
func ParseDefaultConfig(prim toml.Primitive) (planr.InheritableConfig, error) {
- config := GtestDefaults{}
+ config := Defaults{}
if err := toml.PrimitiveDecode(prim, &config); err != nil {
return nil, err
diff --git a/adapters/gtest/executable.go b/adapters/gtest/executable.go
new file mode 100644
index 0000000..25c83c1
--- /dev/null
+++ b/adapters/gtest/executable.go
@@ -0,0 +1,185 @@
+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,
+ )
+ }
+
+ if len(results) > 1 {
+ log.Fatalf("Unexpected number of results, filter should have produced one result")
+ }
+
+ 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 c49f170..41c54c1 100644
--- a/adapters/gtest/templating.go
+++ b/adapters/gtest/templating.go
@@ -3,17 +3,18 @@ package gtest
import (
"io"
"log"
- "text/template"
"os"
+ "golang.flu0r1ne.net/planr"
+ "text/template"
)
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()
@@ -27,45 +28,67 @@ func genCmake(out string, units []cmakeUnit) {
log.Fatalf("Could not open CMakeFile (%s)\n%v", out, err)
}
- writeBoiler(file)
+ writeCmakeBoilerplate(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);
+ 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(`
+
+################################################
+
+## {{.ExeNm}}
+
add_executable(
- "{{.Cname}}"
+ "{{.ExeNm}}"
"{{.File}}"
{{.Srcs}}
)
target_link_libraries(
- "{{.Cname}}"
+ "{{.ExeNm}}"
gtest_main
)
gtest_discover_tests(
- "{{.Cname}}"
+ "{{.ExeNm}}"
)
`)
if err != nil {
- log.Fatalf("Cannot load Gtest Unit Template %v", err)
+ log.Fatalf("Cannot load Gtest unit template %v", err)
}
return tmpl
}
-func writeBoiler(w io.Writer) {
- w.Write([]byte(`
+const GOOGLE_TEST_URL = "https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip"
+
+func writeCmakeBoilerplate(w io.Writer) {
+ tmpl := boilderTemplate()
+
+ tmpl.Execute(w, struct {
+ Url string
+ Version string
+ }{
+ Url: GOOGLE_TEST_URL,
+ Version: planr.VERSION,
+ })
+}
+
+func boilderTemplate() *template.Template {
+ tmpl, err := template.New("gtest_boilerplate").Parse(`
+# AUTOMATICALLY GENERATED BY PLANR VERSION {{.Version}}
+
cmake_minimum_required (VERSION 3.1.0)
project(PlanRGtestAdapter)
@@ -73,12 +96,16 @@ project(PlanRGtestAdapter)
include(FetchContent)
FetchContent_Declare(
googletest
- URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
+ URL {{.Url}}
)
include(GoogleTest)
FetchContent_MakeAvailable(googletest)
-`))
-}
-
+`)
+
+ if err != nil {
+ log.Fatalf("Cannot load Gtest Cmake boilerplate")
+ }
+ return tmpl
+}