aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlu0r1ne <flur01ne@flu0r1ne.net>2021-08-04 14:32:22 -0500
committerFlu0r1ne <flur01ne@flu0r1ne.net>2021-08-04 14:32:22 -0500
commitf90a14d5d723c5d2b87f2eaa19f441dec33bb9b2 (patch)
treee0abd76b6ebd9adcc60732d532cb68c512b0c2d1
parenta0b020a78eb0b33965c59460fc093c6959216e44 (diff)
downloadplanr-f90a14d5d723c5d2b87f2eaa19f441dec33bb9b2.tar.xz
planr-f90a14d5d723c5d2b87f2eaa19f441dec33bb9b2.zip
Prototyped build pipeline
-rw-r--r--adapters.go2
-rw-r--r--adapters/gtest_adapter.go149
-rw-r--r--adapters/template_defs.go50
-rw-r--r--cmd/sub/build.go2
-rw-r--r--fs.go108
-rw-r--r--fs_test.go24
-rw-r--r--runner.go19
-rw-r--r--softscript.go42
-rw-r--r--testcase.go10
9 files changed, 357 insertions, 49 deletions
diff --git a/adapters.go b/adapters.go
index b9c8d9c..7c7dd0b 100644
--- a/adapters.go
+++ b/adapters.go
@@ -19,6 +19,8 @@ type Adapter interface {
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
diff --git a/adapters/gtest_adapter.go b/adapters/gtest_adapter.go
index 4cbaa9c..fd06a48 100644
--- a/adapters/gtest_adapter.go
+++ b/adapters/gtest_adapter.go
@@ -1,41 +1,85 @@
package adapters
import (
- "fmt"
+ "bytes"
+ "log"
+ "os"
+ "path"
+ "strings"
+ "text/template"
+
"github.com/BurntSushi/toml"
"golang.flu0r1ne.net/planr"
- "log"
)
/*
CONFIGURATION
*/
-
type GtestDefaults struct {
- Name *string
- Suite *string
- File *string
+ Name *string
+ Suite *string
+ Testfile *string
+ Test_root *string
+ Srcs *[]string
+ Srcs_root *string
}
func (child *GtestDefaults) Inherit(p interface{}) {
parent := p.(*GtestDefaults)
- if(child.Name == nil) {
- child.Name = parent.Name
- }
+ 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 }
+}
+
+
+type GtestConfig struct {
+ GtestDefaults
+}
+
- if(child.Suite == nil) {
- child.Suite = parent.Suite
+func (cfg GtestConfig) joinTests(path_ string) string {
+ if cfg.Test_root == nil {
+ return planr.JoinConfigDir("tests", path_)
}
+
+ return planr.JoinConfigDir(*cfg.Test_root, path_)
+}
- if(child.File == nil) {
- child.File = parent.File
+func (cfg GtestConfig) joinSrcs(path_ string) string {
+ if cfg.Srcs_root == nil {
+ return planr.JoinConfigDir("../src", path_)
}
+
+ return planr.JoinConfigDir(*cfg.Srcs_root, path_)
}
+func (cfg GtestConfig) srcList() string {
+ var srcList string
-type GtestConfig struct {
- GtestDefaults
+ 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 ")
+ }
+
+ 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{}) {
@@ -64,34 +108,81 @@ func ParseDefaultConfig(prim toml.Primitive) planr.InheritableConfig {
BUILD PROCESS
*/
-type GtestAdapter struct {}
+type GtestAdapter struct {
+ unitTmpl *template.Template
+ cbuf bytes.Buffer
+}
-func (a GtestAdapter) Config() planr.AdapterConfig {
+func (a *GtestAdapter) Config() planr.AdapterConfig {
return planr.AdapterConfig {
Name: "gtest",
ParseConfig: ParseConfig,
ParseDefaultConfig: ParseDefaultConfig,
- }
+ }
}
-func (a GtestAdapter) InitializeBuild() {
- fmt.Println("Initializing");
+func (a *GtestAdapter) InitializeBuild() {
+ a.unitTmpl = UnitTemplate()
+ a.cbuf = bytes.Buffer{}
+
+ WriteCMakeBoiler(&a.cbuf)
}
-func (a GtestAdapter) Build(tc planr.TestCase) {
- fmt.Printf("Building %v\n", tc);
+const GTEST_CMAKE = "CMakeLists.txt"
+
+func Chdir(dir string) {
+ if err := os.Chdir(dir); err != nil {
+ log.Fatal(err)
+ }
}
-func (a GtestAdapter) FinalizeBuild() {
- fmt.Println("Finalizing");
+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) Evaluate(tc planr.TestCase) planr.TestResult {
- fmt.Printf("Evaluating %v\n", tc);
+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)
+ }
- return planr.TestResult {}
+ file.Write(a.cbuf.Bytes())
+
+ Chdir(dir)
+
+ planr.RunCmd("cmake", "-S", ".", "-B", ".")
}
-func (a GtestAdapter) Cleanup() {
- fmt.Printf("Cleaning\n")
+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/template_defs.go b/adapters/template_defs.go
new file mode 100644
index 0000000..54f2840
--- /dev/null
+++ b/adapters/template_defs.go
@@ -0,0 +1,50 @@
+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/cmd/sub/build.go b/cmd/sub/build.go
index 84201a2..19220d3 100644
--- a/cmd/sub/build.go
+++ b/cmd/sub/build.go
@@ -9,7 +9,7 @@ func Build(params []string) {
gtestAdapter := adapters.GtestAdapter {}
r := planr.Runner{}
- r.RegisterAdapter(gtestAdapter)
+ r.RegisterAdapter(&gtestAdapter)
rd := planr.RubricDir()
diff --git a/fs.go b/fs.go
index 905d6d1..978c291 100644
--- a/fs.go
+++ b/fs.go
@@ -6,7 +6,7 @@ import (
"io"
"path"
"path/filepath"
- // "fmt"
+ "strings"
)
/* CONFIG DIRECTORY */
@@ -50,7 +50,7 @@ func directoryExists(path string) bool {
// 1. PlANR_DIRECTORY env if set
// 2. planr
// 3. .planr
-func configDir() string {
+func ConfigDir() string {
// Return environmental override if set
if dir, isSet := os.LookupEnv("PLANR_DIRECTORY"); isSet {
@@ -95,9 +95,22 @@ func configDir() string {
return rubricDir
}
+func JoinConfigDir(path_ string, file string) string {
+ if path.IsAbs(path_) {
+ return path.Join(path_, file)
+ }
+
+ return path.Join(ConfigDir(), path_, file)
+}
+
+
+func RootDir() string {
+ return path.Join(ConfigDir(), "..")
+}
+
// Find rubric directory at PLANR_DIR/rubric
func RubricDir() string {
- rubricDir := path.Join(configDir(), "rubric");
+ rubricDir := path.Join(ConfigDir(), "rubric");
if !directoryExists(rubricDir) {
log.Fatal("Could not find the rubric directory inside of planr")
@@ -106,21 +119,95 @@ func RubricDir() string {
return rubricDir
}
+func BuildDir() string {
+ buildDir := path.Join(ConfigDir(), "build")
+
+ if !directoryExists(buildDir) {
+ err := os.Mkdir(buildDir, 0755)
+
+ if err != nil {
+ log.Fatalf("Cannot create build directory %v\n", err)
+ }
+ }
+
+ return buildDir
+}
+
+func CleanBuildDir() {
+ buildDir := path.Join(ConfigDir(), "build")
+ if err := os.RemoveAll(buildDir); err != nil {
+ log.Fatalf("Cannot clean (removeAll) in build directory %v\n", err)
+ }
+}
+
+func (ac AdapterConfig) ConfigDir() string {
+ dir := BuildDir()
+ dir = path.Join(dir, ac.Name)
+
+ if !directoryExists(dir) {
+ err := os.Mkdir(dir, 0755)
+
+ if err != nil {
+ log.Fatalf("Cannot create build/%s directory %v\n", ac.Name, err)
+ }
+ }
+
+ return dir
+}
+
+func basename(path string) string {
+ ext := filepath.Ext(path)
+ return path[0:len(path) - len(ext)]
+}
+
+func cname(root string, path string) string {
+ rel, err := filepath.Rel(root, path)
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ rel = filepath.ToSlash(rel)
+ parts := strings.Split(rel, "/")
+ n := len(parts)
+
+ if n == 0 {
+ return ""
+ }
+
+ parts[n-1] = basename(parts[n-1])
+
+ return strings.Join(parts, ".")
+}
+
+func collectUnits(root string, cfgs []AdapterConfig) []TestCase {
+ tcs := make([]TestCase, 0)
+
+ collectFromDir(root, nil, cfgs, &tcs)
+
+ for i := range tcs {
+ tcs[i].Cname = cname(root, tcs[i].Path)
+ }
+
+ return tcs
+}
+
+const DEFAULTS = "defaults.toml"
// Collects the units from the configuration tree
// TODO: Cleanup
-func collectUnits(
- name string,
+func collectFromDir(
+ dir string,
defaults *Defaults,
cfgs []AdapterConfig,
units *[]TestCase,
) {
- fp, err := os.Open(name)
+ fp, err := os.Open(dir)
if err != nil {
log.Fatal(err)
}
// Process defaults for this directory if a defaults.toml is found
- defaultsPath := path.Join(name, "defaults.toml")
+ defaultsPath := path.Join(dir, DEFAULTS)
if info, err := os.Stat(defaultsPath); err == nil && !info.IsDir() {
d := DecodeDefaults(defaultsPath, cfgs)
@@ -143,13 +230,13 @@ func collectUnits(
for _, ent := range dirs {
- child := path.Join(name, ent.Name())
+ child := path.Join(dir, ent.Name())
nm := ent.Name()
if ent.IsDir() {
- collectUnits(child, defaults, cfgs, units)
+ collectFromDir(child, defaults, cfgs, units)
} else {
- if nm == "defaults.toml" {
+ if nm == DEFAULTS {
continue
}
@@ -159,7 +246,6 @@ func collectUnits(
tc := TestCase {
Path: child,
- Cname: nm,
Config: config,
}
diff --git a/fs_test.go b/fs_test.go
new file mode 100644
index 0000000..4e36469
--- /dev/null
+++ b/fs_test.go
@@ -0,0 +1,24 @@
+package planr
+
+import (
+ "testing"
+)
+
+func TestCname(t *testing.T) {
+ ROOT := "/home/rubric"
+
+ v := [] struct { path, cname string } {
+ {"/home/rubric/tc1.toml", "tc1" },
+ {"/home/rubric/alpha/tc1.toml", "alpha.tc1" },
+ {"/home/rubric/alpha/beta/tc2.toml", "alpha.beta.tc2"},
+ {"/home/rubric/.a/_b./.abcd.toml", ".a._b...abcd"},
+ }
+
+ for _, vec := range v {
+ got := cname(ROOT, vec.path)
+
+ if vec.cname != got {
+ t.Fatalf("Cname(%s) = %s, wanted %s", vec.path, got, vec.cname)
+ }
+ }
+}
diff --git a/runner.go b/runner.go
index 5c66573..1470fbd 100644
--- a/runner.go
+++ b/runner.go
@@ -1,5 +1,9 @@
package planr
+import (
+ "fmt"
+)
+
type Runner struct {
adapters []Adapter
}
@@ -18,13 +22,8 @@ func (r Runner) adapterCfgs() []AdapterConfig {
return cgs
}
-func (r Runner) collectUnits(root string) []TestCase {
- tcs := make([]TestCase, 10)
-
- collectUnits(root, nil, r.adapterCfgs(), &tcs)
-
- return tcs
-}
+// [Initialization] -> [Generation] -> [Finalization] ->
+// [Build] -> [Evaluation] -> [Clean]
func (r Runner) cycle(tcs []TestCase) []TestResult {
results := make([]TestResult, 0)
@@ -36,14 +35,18 @@ func (r Runner) cycle(tcs []TestCase) []TestResult {
for _, tc := range tcs {
if tc.ContainsAdapter(aname) {
+ fmt.Printf("[R] Building %s\n", tc.Path)
adapter.Build(tc)
}
}
adapter.FinalizeBuild()
+ adapter.Make()
+
for _, tc := range tcs {
if tc.ContainsAdapter(aname) {
+ fmt.Printf("[R] Evaluating %s\n", tc.Path)
results = append(results, adapter.Evaluate(tc))
}
}
@@ -55,7 +58,7 @@ func (r Runner) cycle(tcs []TestCase) []TestResult {
}
func (r Runner) Run(root string) [] TestResult {
- tcs := r.collectUnits(root)
+ tcs := collectUnits(root, r.adapterCfgs())
trs := r.cycle(tcs)
diff --git a/softscript.go b/softscript.go
new file mode 100644
index 0000000..9924d54
--- /dev/null
+++ b/softscript.go
@@ -0,0 +1,42 @@
+package planr
+
+import (
+ "io"
+ "os"
+ "os/exec"
+ "log"
+)
+
+func RunCmd(name string, args ...string) {
+ cmd := exec.Command(name, args...)
+
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ log.Fatal(err);
+ }
+
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ log.Fatal(err);
+ }
+
+ go func() {
+ io.Copy(os.Stderr, stderr)
+ }()
+
+ go func() {
+ io.Copy(os.Stdout, stdout)
+ }()
+
+ if err := cmd.Start(); err != nil {
+ log.Fatal(err)
+ }
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if err := cmd.Wait(); err != nil {
+ log.Fatalf("Could not execute cmake command\n%v", err)
+ }
+}
diff --git a/testcase.go b/testcase.go
index 02db738..70c188e 100644
--- a/testcase.go
+++ b/testcase.go
@@ -5,7 +5,13 @@ type TestResult struct {
}
type TestCase struct {
+ // absolute path to the test case configuration
Path string
+ // The canonical name is a semantically meaningful name
+ // guaranteed to be unique among the tests
+ // Obtained by replacing separators in the relative path of the
+ // configuration with dots. The `toml` extension is also stripped.
+ // rubric/alpha/beta/tc1.toml -> alpha.beta.tc1
Cname string
Config TestCaseConfig
}
@@ -19,3 +25,7 @@ func (tc TestCase) ContainsAdapter(name string) bool {
return false;
}
+
+func (tc TestCase) AdapterConfig(name string) InheritableConfig {
+ return tc.Config.adapters_[name]
+}