diff --git a/CHANGELOG.md b/CHANGELOG.md index 645de56c..6e9a9df9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,7 +99,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 removes a limit on the number of overlays that could be included in a node or profile. #852, #876, #883, #896, #903 - During node discovery, prefer the primary network interface, if defined. #775 - +- added general test framework which creates temporary directories on the fly ## [4.4.0] 2023-01-18 ### Added diff --git a/internal/app/wwctl/node/set/main_test.go b/internal/app/wwctl/node/set/main_test.go index 4feda0d3..0a386b4a 100644 --- a/internal/app/wwctl/node/set/main_test.go +++ b/internal/app/wwctl/node/set/main_test.go @@ -2,25 +2,78 @@ package set import ( "bytes" - "os" "testing" - warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" - "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/warewulfd" + "github.com/hpcng/warewulf/internal/pkg/testenv" "github.com/stretchr/testify/assert" ) -func Test_Add(t *testing.T) { - tests := []struct { - name string - args []string - wantErr bool - stdout string - chkout bool - outDb string - inDB string - }{ +type test_description struct { + name string + args []string + wantErr bool + stdout string + inDB string + outDb string +} + +func run_test(t *testing.T, test test_description) { + env := testenv.New(t) + defer env.RemoveAll(t) + + env.WriteFile(t, "etc/warewulf/nodes.conf", test.inDB) + warewulfd.SetNoDaemon() + name := test.name + if name == "" { + name = t.Name() + } + t.Run(name, func(t *testing.T) { + baseCmd := GetCommand() + test.args = append(test.args, "--yes") + baseCmd.SetArgs(test.args) + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + err := baseCmd.Execute() + if test.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, buf.String(), test.stdout) + content := env.ReadFile(t, "etc/warewulf/nodes.conf") + assert.Equal(t, test.outDb, content) + } + }) +} + +func Test_Single_Node_Change_Profile(t *testing.T) { + test := test_description{ + args: []string{"--profile=foo", "n01"}, + wantErr: false, + stdout: "", + inDB: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default`, + outDb: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - foo +`} + run_test(t, test) +} + +func Test_Multiple_Add_Tests(t *testing.T) { + tests := []test_description{ {name: "single node change profile", args: []string{"--profile=foo", "n01"}, wantErr: false, @@ -296,53 +349,7 @@ nodes: path: /var `}, } - conf_yml := `WW_INTERNAL: 0` - tempWarewulfConf, warewulfConfErr := os.CreateTemp("", "warewulf.conf-") - assert.NoError(t, warewulfConfErr) - defer os.Remove(tempWarewulfConf.Name()) - _, warewulfConfErr = tempWarewulfConf.Write([]byte(conf_yml)) - assert.NoError(t, warewulfConfErr) - assert.NoError(t, tempWarewulfConf.Sync()) - assert.NoError(t, warewulfconf.New().Read(tempWarewulfConf.Name())) - - tempNodeConf, nodesConfErr := os.CreateTemp("", "nodes.conf-") - assert.NoError(t, nodesConfErr) - defer os.Remove(tempNodeConf.Name()) - node.ConfigFile = tempNodeConf.Name() - warewulfd.SetNoDaemon() for _, tt := range tests { - var err error - _, err = tempNodeConf.Seek(0, 0) - assert.NoError(t, err) - assert.NoError(t, tempNodeConf.Truncate(0)) - _, err = tempNodeConf.Write([]byte(tt.inDB)) - assert.NoError(t, err) - assert.NoError(t, tempNodeConf.Sync()) - t.Logf("Running test: %s\n", tt.name) - t.Run(tt.name, func(t *testing.T) { - baseCmd := GetCommand() - tt.args = append(tt.args, "--yes") - baseCmd.SetArgs(tt.args) - buf := new(bytes.Buffer) - baseCmd.SetOut(buf) - baseCmd.SetErr(buf) - err = baseCmd.Execute() - if (err != nil) != tt.wantErr { - t.Errorf("Got unwanted error: %s", err) - t.FailNow() - } - config, configErr := node.New() - assert.NoError(t, configErr) - dumpBytes, _ := config.Dump() - dump := string(dumpBytes) - if dump != tt.outDb { - t.Errorf("DB dump is wrong, got:'%s'\nwant:'%s'", dump, tt.outDb) - t.FailNow() - } - if tt.chkout && buf.String() != tt.stdout { - t.Errorf("Got wrong output, got:'%s'\nwant:'%s'", buf.String(), tt.stdout) - t.FailNow() - } - }) + run_test(t, tt) } } diff --git a/internal/pkg/testenv/testenv.go b/internal/pkg/testenv/testenv.go new file mode 100644 index 00000000..a9889b50 --- /dev/null +++ b/internal/pkg/testenv/testenv.go @@ -0,0 +1,139 @@ +// Package testenv provides functions and data structures for +// constructing and manipulating a temporary Warewulf environment for +// use during automated testing. +// +// The testenv package should only be used in tests. +package testenv + +import ( + "os" + "path" + "path/filepath" + "testing" + + "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/stretchr/testify/assert" +) + +const initWarewulfConf = `WW_INTERNAL: 0` +const initDefaultsConf = `WW_INTERNAL: 43` +const initNodesConf = `WW_INTERNAL: 43 +nodeprofiles: + default: {} +nodes: + node1: {} +` + +type TestEnv struct { + BaseDir string +} + +// New creates a test environment in a temporary directory and configures +// Warewulf to use it. +// +// Caller is responsible to delete env.BaseDir by calling +// env.RemoveAll. Note that this does not restore Warewulf to its +// previous state. +// +// Asserts no errors occur. +func New(t *testing.T) (env *TestEnv) { + env = new(TestEnv) + + tmpDir, err := os.MkdirTemp(os.TempDir(), "ww4test-*") + assert.NoError(t, err) + env.BaseDir = tmpDir + + env.WriteFile(t, "etc/warewulf/nodes.conf", initNodesConf) + env.WriteFile(t, "etc/warewulf/warewulf.conf", initWarewulfConf) + env.WriteFile(t, "share/warewulf/defaults.conf", initDefaultsConf) + + // re-read warewulf.conf + conf := config.New() + err = conf.Read(env.GetPath("etc/warewulf/warewulf.conf")) + assert.NoError(t, err) + + conf.Paths.Sysconfdir = env.GetPath("etc") + conf.Paths.Bindir = env.GetPath("bin") + conf.Paths.Datadir = env.GetPath("share") + conf.Paths.Localstatedir = env.GetPath("var/local") + conf.Paths.Srvdir = env.GetPath("srv") + conf.Paths.Tftpdir = env.GetPath("srv/tftp") + conf.Paths.Firewallddir = env.GetPath("usr/lib/firewalld/services") + conf.Paths.Systemddir = env.GetPath("usr/lib/systemd/system") + conf.Paths.WWOverlaydir = env.GetPath(path.Join(conf.Paths.Localstatedir, "warewulfoverlays")) + conf.Paths.WWChrootdir = env.GetPath(path.Join(conf.Paths.Localstatedir, "warewulf/chroots")) + conf.Paths.WWProvisiondir = env.GetPath(path.Join(conf.Paths.Srvdir, "warewulf")) + conf.Paths.WWClientdir = env.GetPath("warewulf") + + for _, confPath := range []string{ + conf.Paths.Sysconfdir, + conf.Paths.Bindir, + conf.Paths.Datadir, + conf.Paths.Localstatedir, + conf.Paths.Srvdir, + conf.Paths.Tftpdir, + conf.Paths.Firewallddir, + conf.Paths.Systemddir, + conf.Paths.WWOverlaydir, + conf.Paths.WWChrootdir, + conf.Paths.WWProvisiondir, + conf.Paths.WWClientdir, + }{ + env.MkdirAll(t, confPath) + } + + // node.init() has already run, so set the config path again + node.ConfigFile = env.GetPath("etc/warewulf/nodes.conf") + + return +} + +// GetPath returns the absolute path name for fileName specified +// relative to the test environment. +func (env *TestEnv) GetPath(fileName string) string { + return path.Join(env.BaseDir, fileName) +} + +// MkdirAll creates dirName and any intermediate directories relative +// to the test environment. +// +// Asserts no errors occur. +func (env *TestEnv) MkdirAll(t *testing.T, dirName string) { + err := os.MkdirAll(env.GetPath(dirName), 0755) + assert.NoError(t, err) +} + +// WriteFile writes content to fileName, creating any necessary +// intermediate directories relative to the test environment. +// +// Asserts no errors occur. +func (env *TestEnv) WriteFile(t *testing.T, fileName string, content string) { + dirName := filepath.Dir(fileName) + env.MkdirAll(t, dirName) + + f, err := os.Create(env.GetPath(fileName)) + assert.NoError(t, err) + defer f.Close() + _, err = f.WriteString(content) + assert.NoError(t, err) +} + +// ReadFile returns the content of fileName as converted to a +// string. +// +// Asserts no errors occur. +func (env *TestEnv) ReadFile(t *testing.T, fileName string) string { + buffer, err := os.ReadFile(env.GetPath(fileName)) + assert.NoError(t, err) + return string(buffer) +} + +// RemoveAll deletes the temporary directory, and all its contents, +// for the test environment. +// +// Asserts no errors occur. +func (env *TestEnv) RemoveAll(t *testing.T) { + err := os.RemoveAll(env.BaseDir) + assert.NoError(t, err) +} diff --git a/internal/pkg/testenv/testenv_test.go b/internal/pkg/testenv/testenv_test.go new file mode 100644 index 00000000..85d6ce2d --- /dev/null +++ b/internal/pkg/testenv/testenv_test.go @@ -0,0 +1,35 @@ +package testenv + +import ( + "testing" + + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/stretchr/testify/assert" +) + +func Test_Basic(t *testing.T) { + env := New(t) + defer env.RemoveAll(t) + nodedb, err := node.New() + assert.NoError(t, err) + nodes, err := nodedb.FindAllNodes() + assert.NoError(t, err) + assert.Len(t, nodes, 1) +} + +func Test_two_nodes(t *testing.T) { + env := New(t) + env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43 +nodeprofiles: + default: {} +nodes: + node1: {} + node2: {} +`) + defer env.RemoveAll(t) + nodedb, err := node.New() + assert.NoError(t, err) + nodes, err := nodedb.FindAllNodes() + assert.NoError(t, err) + assert.Len(t, nodes, 2) +}