diff options
Diffstat (limited to 'adapters')
| -rw-r--r-- | adapters/gtest/adapter.go | 187 | ||||
| -rw-r--r-- | adapters/gtest/config.go | 58 | ||||
| -rw-r--r-- | adapters/gtest/executable.go | 186 | ||||
| -rw-r--r-- | adapters/gtest/templating.go | 14 | 
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}}"  )  `)  | 
