testmark-go

Merge pull request #7 from warpfork/testexec-then-feature

commit 99fe56e3128ac8b089225aad441b0e3081028476 · parent a217051880 · parent 23ec2b65f4 · browse this tree
Eric Myhre, 2021-10-21 19:49:08 UTC

testexec: subtest ("then-*") feature
diff --git a/testexec/selfexercise.md b/testexec/selfexercise.md
new file mode 100644
index 0000000..99fa451
--- /dev/null
+++ b/testexec/selfexercise.md
@@ -0,0 +1,81 @@
+testexec selfexercise file
+==========================
+
+We will run a script with some files provisioned:
+
+[testmark]:# (whee/fs/a)
+```
+body-a
+```
+
+The script is simple:
+
+[testmark]:# (whee/script)
+```
+echo hello
+ls
+```
+
+The output is unsurprising:
+
+[testmark]:# (whee/output)
+```
+hello
+a
+```
+
+---
+
+We can make subtests which add more files:
+
+[testmark]:# (whee/then-more-files/fs/b)
+```
+body-b
+```
+
+[testmark]:# (whee/then-more-files/script)
+```
+ls
+```
+
+[testmark]:# (whee/then-more-files/output)
+```
+a
+b
+```
+
+---
+
+We can also affect the filesystem in subtests:
+
+[testmark]:# (whee/then-touching-files/script)
+```
+echo "body-c" > ./c
+ls
+```
+
+Notice that this doesn't inherit the file "b" from the last test --
+this is because they're siblings, so they each got a copy of the *parent* filesystem state.
+
+[testmark]:# (whee/then-touching-files/output)
+```
+a
+c
+```
+
+---
+
+If we make a subtest of *that* subtest, it also inherits a copy of those effects:
+
+[testmark]:# (whee/then-touching-files/then-subtesting-again/script)
+```
+ls
+cat ./c
+```
+
+[testmark]:# (whee/then-touching-files/then-subtesting-again/output)
+```
+a
+c
+body-c
+```
diff --git a/testexec/testexec.go b/testexec/testexec.go
index f59ddb2..d7214bc 100644
--- a/testexec/testexec.go
+++ b/testexec/testexec.go
@@ -141,20 +141,20 @@ func (tcfg *Tester) init() {
 // the test will still be failed, even though in patch regen mode most assertions are usually skipped.
 func (tcfg Tester) TestSequence(t *testing.T, data testmark.DirEnt) {
 	t.Helper()
-	tcfg.test(t, data, true, false)
+	tcfg.test(t, data, true, false, "")
 }
 
 func (tcfg Tester) TestScript(t *testing.T, data testmark.DirEnt) {
 	t.Helper()
-	tcfg.test(t, data, false, true)
+	tcfg.test(t, data, false, true, "")
 }
 
 func (tcfg Tester) Test(t *testing.T, data testmark.DirEnt) {
 	t.Helper()
-	tcfg.test(t, data, true, true)
+	tcfg.test(t, data, true, true, "")
 }
 
-func (tcfg Tester) test(t *testing.T, data testmark.DirEnt, allowExec, allowScript bool) {
+func (tcfg Tester) test(t *testing.T, data testmark.DirEnt, allowExec, allowScript bool, parentTmpdir string) {
 	t.Helper()
 	tcfg.init()
 
@@ -179,9 +179,11 @@ func (tcfg Tester) test(t *testing.T, data testmark.DirEnt, allowExec, allowScri
 	}
 
 	// Create a tempdir, and fill it with any files.
-	if fsEnt, exists := data.Children["fs"]; exists {
+	var dir string
+	if fsEnt, exists := data.Children["fs"]; exists || parentTmpdir != "" {
 		// Create and get into a tempdir.
-		dir, err := ioutil.TempDir("", "testmarkexec")
+		var err error
+		dir, err = ioutil.TempDir("", "testmarkexec")
 		if err != nil {
 			t.Errorf("test aborted: could not create tempdir: %s", err)
 		}
@@ -195,9 +197,18 @@ func (tcfg Tester) test(t *testing.T, data testmark.DirEnt, allowExec, allowScri
 			t.Errorf("test aborted: could not chdir to tempdir: %s", err)
 		}
 
-		// Create files.
-		if err := createFiles(fsEnt, "."); err != nil {
-			t.Errorf("test aborted: could not populate files to tempdir: %s", err)
+		// If there was a parent tempdir: copy those files first.
+		if parentTmpdir != "" {
+			if err := copyFiles(parentTmpdir, dir); err != nil {
+				t.Errorf("test aborted: could not populate files to tempdir: %s", err)
+			}
+		}
+
+		// Create any new files.
+		if fsEnt != nil {
+			if err := createFiles(fsEnt, "."); err != nil {
+				t.Errorf("test aborted: could not populate files to tempdir: %s", err)
+			}
 		}
 	}
 
@@ -273,7 +284,20 @@ func (tcfg Tester) test(t *testing.T, data testmark.DirEnt, allowExec, allowScri
 		}
 	})
 
-	// TODO: look for "then-*" dirs.
+	// Look for "then-*" dirs.
+	//  If we're already failed -- make the run block, but skip it.
+	//  If we're going to procede: make a new tempdir, copy the contents, and then procede by recursing.
+	for _, child := range data.ChildrenList {
+		if len(child.Name) > 5 && strings.HasPrefix(child.Name, "then-") {
+			alreadyFailed := t.Failed()
+			t.Run(child.Name, func(t *testing.T) {
+				if alreadyFailed {
+					t.Skipf("parent commands failed, so while more commands are specified, testing them is not meaningful")
+				}
+				tcfg.test(t, child, allowExec, allowScript, dir)
+			})
+		}
+	}
 
 }
 
@@ -326,3 +350,39 @@ func createFiles(dir *testmark.DirEnt, prefix string) error {
 	}
 	return nil
 }
+
+// copyFiles does what it says on the tin, recursively,
+// and handles dirs, files, and symlinks, with basic posix perms (the 0777 bits).
+// It doesn't pay attention to dev nodes, uid/gid, etc etc.
+func copyFiles(source, destination string) error {
+	var err error = filepath.Walk(source, func(path string, fileInfo os.FileInfo, err error) error {
+		var relPath string = strings.Replace(path, source, "", 1)
+		if relPath == "" {
+			return nil
+		}
+		switch fileInfo.Mode() & os.ModeType {
+		case os.ModeDir:
+			return os.Mkdir(filepath.Join(destination, relPath), fileInfo.Mode())
+		case os.ModeSymlink:
+			link, err := os.Readlink(path)
+			if err != nil {
+				return err
+			}
+			return os.Symlink(link, filepath.Join(destination, relPath))
+		default:
+			fnew, err := os.OpenFile(filepath.Join(destination, relPath), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, fileInfo.Mode())
+			if err != nil {
+				return err
+			}
+			defer fnew.Close()
+			fold, err := os.OpenFile(path, os.O_RDONLY, 0)
+			if err != nil {
+				return err
+			}
+			defer fold.Close()
+			_, err = io.Copy(fnew, fold)
+			return err
+		}
+	})
+	return err
+}
diff --git a/testexec/testexec_test.go b/testexec/testexec_test.go
new file mode 100644
index 0000000..0701f4a
--- /dev/null
+++ b/testexec/testexec_test.go
@@ -0,0 +1,28 @@
+package testexec_test
+
+import (
+	"testing"
+
+	"github.com/warpfork/go-testmark"
+	"github.com/warpfork/go-testmark/testexec"
+)
+
+func Test(t *testing.T) {
+	filename := "selfexercise.md"
+	doc, err := testmark.ReadFile(filename)
+	if err != nil {
+		t.Fatalf("spec file parse failed?!: %s", err)
+	}
+
+	doc.BuildDirIndex()
+	patches := testmark.PatchAccumulator{}
+	for _, dir := range doc.DirEnt.ChildrenList {
+		t.Run(dir.Name, func(t *testing.T) {
+			test := testexec.Tester{
+				Patches: &patches,
+			}
+			test.TestScript(t, dir)
+		})
+	}
+	patches.WriteFileWithPatches(doc, filename)
+}