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/gtest/executable.go | 186 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 adapters/gtest/executable.go (limited to 'adapters/gtest/executable.go') 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 +} + -- cgit v1.2.3 From c356d233bab7b4bb6cc29c1c03abadf111b40d0e Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Sun, 5 Sep 2021 01:00:12 -0500 Subject: Remove pointers and cleanup templating, add version info --- adapters/gtest/config.go | 27 +++++++++++++-------------- adapters/gtest/executable.go | 15 +++++++-------- adapters/gtest/templating.go | 41 ++++++++++++++++++++++++++++++++++------- 3 files changed, 54 insertions(+), 29 deletions(-) (limited to 'adapters/gtest/executable.go') diff --git a/adapters/gtest/config.go b/adapters/gtest/config.go index 4cd2030..b8a196b 100644 --- a/adapters/gtest/config.go +++ b/adapters/gtest/config.go @@ -13,21 +13,21 @@ const ( ) type Defaults struct { - Name *string - Suite *string - Testfile *string + Name string + Suite string + Testfile string Srcs []string - Timeout *uint + Timeout uint } 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.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 == nil) { child.Timeout = parent.Timeout } + if(child.Timeout == 0) { child.Timeout = parent.Timeout } } @@ -36,17 +36,16 @@ type Config struct { } func (c * Config) finalize(path string) { - if c.Name == nil { + if c.Name == "" { log.Fatalf("\"name\" is not defined for unit: %s\n", path) - } else if c.Suite == nil { + } else if c.Suite == "" { log.Fatalf("\"suite\" is not defined for unit: %s\n", path) - } else if c.Testfile == nil { + } else if c.Testfile == "" { log.Fatalf("\"testfile\" is not defined for unit: %s\n", path) } - if c.Timeout == nil { - c.Timeout = new(uint) - *c.Timeout = DEFAULT_TIMEOUT; + if c.Timeout == 0 { + c.Timeout = DEFAULT_TIMEOUT; } } diff --git a/adapters/gtest/executable.go b/adapters/gtest/executable.go index b8f79ec..feafe97 100644 --- a/adapters/gtest/executable.go +++ b/adapters/gtest/executable.go @@ -27,7 +27,7 @@ func createExecutables(tcs []planr.TestCase) []executable { for _, tc := range tcs { cfg := tc.AdapterConfig().(*Config) - file := *cfg.Testfile + file := cfg.Testfile exe, contained := exes[file] // For set comparison @@ -55,7 +55,7 @@ func createExecutables(tcs []planr.TestCase) []executable { 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.testpath, cfg.Testfile, ) } @@ -119,12 +119,12 @@ func runGtest(exe string, tc planr.TestCase, builddir string) planr.TestResult { log.Fatal(err) } - timeout := time.Duration(*cfg.Timeout) * time.Millisecond + 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 + testFlag := "--gtest_filter=" + cfg.Suite + "." + cfg.Name cmd := exec.CommandContext(ctx, exePath, jsonFlag, testFlag) @@ -151,14 +151,13 @@ func runGtest(exe string, tc planr.TestCase, builddir string) planr.TestResult { 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, + cfg.Name, + cfg.Suite, ) } - // TODO: Cleanup -- ZERO TESTS? if len(results) > 1 { - log.Fatalf("Unexpected number of results") + log.Fatalf("Unexpected number of results, filter should have produced one result") } decodeResult := results[0] diff --git a/adapters/gtest/templating.go b/adapters/gtest/templating.go index 57532fa..da42592 100644 --- a/adapters/gtest/templating.go +++ b/adapters/gtest/templating.go @@ -3,8 +3,9 @@ package gtest import ( "io" "log" - "text/template" "os" + "golang.furkistan.com/planr" + "text/template" ) type cmakeUnit struct { @@ -27,7 +28,7 @@ func generateCmakeScript(out string, units []cmakeUnit) { log.Fatalf("Could not open CMakeFile (%s)\n%v", out, err) } - writeBoiler(file) + writeCmakeBoilerplate(file) tmpl := unitTemplate() @@ -43,6 +44,10 @@ func generateCmakeScript(out string, units []cmakeUnit) { func unitTemplate() *template.Template { tmpl, err := template.New("gtest_unit").Parse(` +################################################ + +## {{.ExeNm}} + add_executable( "{{.ExeNm}}" "{{.File}}" @@ -60,14 +65,30 @@ gtest_discover_tests( `) 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) @@ -75,10 +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 } -- cgit v1.2.3