From 0beb67787b9806b8cffd2ab3b64bf192534e5d45 Mon Sep 17 00:00:00 2001 From: Jonas Finnemann Jensen Date: Mon, 25 Jan 2016 13:36:24 -0800 Subject: [PATCH] More enginetests --- engines/enginetest/testattachvolume.go | 173 +++++++++++++++++++++++++ engines/mock/mockengine_test.go | 133 ++----------------- 2 files changed, 183 insertions(+), 123 deletions(-) create mode 100644 engines/enginetest/testattachvolume.go diff --git a/engines/enginetest/testattachvolume.go b/engines/enginetest/testattachvolume.go new file mode 100644 index 00000000..54988347 --- /dev/null +++ b/engines/enginetest/testattachvolume.go @@ -0,0 +1,173 @@ +// Package enginetest provides utilities for testing generic engine +// implementations. +package enginetest + +import ( + "encoding/json" + "sync" + "testing" + + "github.com/taskcluster/taskcluster-worker/engines" + "github.com/taskcluster/taskcluster-worker/engines/extpoints" +) + +func parseTestPayload(t *testing.T, engine engines.Engine, payload string) interface{} { + jsonPayload := map[string]json.RawMessage{} + err := json.Unmarshal([]byte(payload), &jsonPayload) + if err != nil { + t.Fatal("Test payload parsing failed: ", err, " payload: ", payload) + } + p, err := engine.PayloadSchema().Parse(jsonPayload) + if err != nil { + t.Fatal("Test payload validation failed: ", err, " payload: ", payload) + } + return p +} + +// A VolumeTestCase holds information necessary to run tests that an engine +// can create volumes, mount and read/write to volumes. +type VolumeTestCase struct { + sync.Mutex + Engine string + Mountpoint string + WriteVolumePayload string + CheckVolumePayload string + engine engines.Engine +} + +func nilOrFatal(t *testing.T, err error, a ...interface{}) { + if err != nil { + t.Fatal(append(a, err)...) + } +} + +func nilOrError(t *testing.T, err error, a ...interface{}) { + if err != nil { + t.Error(append(a, err)...) + } +} + +func (c *VolumeTestCase) ensureEngine(t *testing.T) { + c.Lock() + defer c.Unlock() + if c.engine != nil { + return + } + // Find EngineProvider + engineProvider := extpoints.EngineProviders.Lookup(c.Engine) + if engineProvider == nil { + t.Fatal("Couldn't find EngineProvider: ", c.Engine) + } + // Create Engine instance + engine, err := engineProvider(extpoints.EngineOptions{ + Environment: nil, //TODO: Provide something we can use for tests + }) + nilOrFatal(t, err, "Failed to create Engine") + c.engine = engine +} + +func (c *VolumeTestCase) buildRunSandbox(t *testing.T, b engines.SandboxBuilder) bool { + // Start sandbox and wait for result + sandbox, err := b.StartSandbox() + nilOrFatal(t, err, "Failed to start sandbox") + + // Wait for result + resultSet, err := sandbox.WaitForResult() + nilOrFatal(t, err, "WaitForResult failed") + + // Get result and dispose ResultSet + result := resultSet.Success() + nilOrError(t, resultSet.Dispose(), "Failed to dispose of ResultSet: ") + return result +} + +func (c *VolumeTestCase) writeVolume(t *testing.T, volume engines.Volume, readOnly bool) bool { + // Construct SandboxBuilder, Attach volume to sandbox and run it + sandboxBuilder, err := c.engine.NewSandboxBuilder(engines.SandboxOptions{ + TaskContext: nil, // TODO: Create a TaskContext + Payload: parseTestPayload(t, c.engine, c.WriteVolumePayload), + }) + nilOrFatal(t, err, "Error creating SandboxBuilder") + err = sandboxBuilder.AttachVolume(c.Mountpoint, volume, readOnly) + nilOrFatal(t, err, "Failed to attach volume") + return c.buildRunSandbox(t, sandboxBuilder) +} + +func (c *VolumeTestCase) readVolume(t *testing.T, volume engines.Volume, readOnly bool) bool { + // Construct SandboxBuilder, Attach volume to sandbox and run it + sandboxBuilder, err := c.engine.NewSandboxBuilder(engines.SandboxOptions{ + TaskContext: nil, // TODO: Create a TaskContext + Payload: parseTestPayload(t, c.engine, c.CheckVolumePayload), + }) + nilOrFatal(t, err, "Error creating SandboxBuilder") + err = sandboxBuilder.AttachVolume(c.Mountpoint, volume, readOnly) + nilOrFatal(t, err, "Failed to attach volume") + return c.buildRunSandbox(t, sandboxBuilder) +} + +// TestWriteReadVolume tests that we can write and read from a volume +func (c *VolumeTestCase) TestWriteReadVolume(t *testing.T) { + c.ensureEngine(t) + volume, err := c.engine.NewCacheFolder() + nilOrFatal(t, err, "Failed to create a new cache folder") + if !c.writeVolume(t, volume, false) { + t.Fatal("Running with writeVolumePayload didn't finish successfully") + } + if !c.readVolume(t, volume, false) { + t.Fatal("Running with CheckVolumePayload didn't finish successfully, ", + "after we ran writeVolumePayload with same volume (writing something)") + } + nilOrFatal(t, volume.Dispose(), "Failed to dispose cache folder") +} + +// TestReadEmptyVolume tests that read from empty volume doesn't work +func (c *VolumeTestCase) TestReadEmptyVolume(t *testing.T) { + c.ensureEngine(t) + volume, err := c.engine.NewCacheFolder() + nilOrFatal(t, err, "Failed to create a new cache folder") + if c.readVolume(t, volume, false) { + t.Fatal("Running with CheckVolumePayload with an empty volume was successful.", + "It really shouldn't have been.") + } + nilOrFatal(t, volume.Dispose(), "Failed to dispose new cache folder 2") +} + +// TestWriteToReadOnlyVolume tests that write doesn't work to a read-only volume +func (c *VolumeTestCase) TestWriteToReadOnlyVolume(t *testing.T) { + c.ensureEngine(t) + volume, err := c.engine.NewCacheFolder() + nilOrFatal(t, err, "Failed to create a new cache folder") + c.writeVolume(t, volume, true) + if c.readVolume(t, volume, false) { + t.Fatal("Write on read-only volume didn't give us is an issue when reading") + } + nilOrFatal(t, volume.Dispose(), "Failed to dispose cache folder") +} + +// TestReadToReadOnlyVolume tests that we can read from a read-only volume +func (c *VolumeTestCase) TestReadToReadOnlyVolume(t *testing.T) { + c.ensureEngine(t) + volume, err := c.engine.NewCacheFolder() + nilOrFatal(t, err, "Failed to create a new cache folder") + if !c.writeVolume(t, volume, false) { + t.Fatal("Running with writeVolumePayload didn't finish successfully") + } + if !c.readVolume(t, volume, true) { + t.Fatal("Running with CheckVolumePayload didn't finish successfully, ", + "after we ran writeVolumePayload with same volume (writing something) ", + "This was with a readOnly attachment when reading") + } + nilOrFatal(t, volume.Dispose(), "Failed to dispose cache folder") +} + +// Test runs all tests on the test case. +func (c *VolumeTestCase) Test(t *testing.T) { + c.ensureEngine(t) + wg := sync.WaitGroup{} + wg.Add(4) + go func() { c.TestWriteReadVolume(t); wg.Done() }() + go func() { c.TestReadEmptyVolume(t); wg.Done() }() + go func() { c.TestWriteToReadOnlyVolume(t); wg.Done() }() + go func() { c.TestReadToReadOnlyVolume(t); wg.Done() }() + wg.Wait() +} diff --git a/engines/mock/mockengine_test.go b/engines/mock/mockengine_test.go index f5ae7e74..000b6b28 100644 --- a/engines/mock/mockengine_test.go +++ b/engines/mock/mockengine_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/taskcluster/taskcluster-worker/engines" - "github.com/taskcluster/taskcluster-worker/engines/extpoints" + "github.com/taskcluster/taskcluster-worker/engines/enginetest" ) func parseTestPayload(t *testing.T, engine engines.Engine, payload string) interface{} { @@ -21,136 +21,23 @@ func parseTestPayload(t *testing.T, engine engines.Engine, payload string) inter return p } -func TestAttachEngine(t *testing.T) { - // TODO: This is supposed to be arguments to: - // var testcase = enginetest.AttachCacheTestCase{ - // engineName: ..., - // mountpoint: ..., - // ... - // } - // - // So we can do things like: - // - // func TestAssertVolumePayload(t *testing.T) { - // testcase.TestAssertVolumePayload(t) - // } - // func TestAttachVolume(t *testing.T) { - // testcase.TestAttachVolume(t) - // } - // - // This way once we've declared a few simple things we have a test case object - // and we can write a bunch of generic tests and does all sort of stupid - // simple tests using the test payloads given. - engineName := "mock" - mountpoint := "/mock/volume" - writeVolumePayload := `{ +var volumeTestCase = enginetest.VolumeTestCase{ + Engine: "mock", + Mountpoint: "/mock/volume", + WriteVolumePayload: `{ "start": { "delay": 10, "function": "set-volume", "argument": "/mock/volume" } - }` - assertVolumePayload := `{ + }`, + CheckVolumePayload: `{ "start": { "delay": 10, "function": "get-volume", "argument": "/mock/volume" } - }` - - t.Parallel() - - // Find EngineProvider - engineProvider := extpoints.EngineProviders.Lookup(engineName) - if engineProvider == nil { - t.Fatal("Couldn't find EngineProvider: ", engineName) - } - - // Create Engine instance - engine, err := engineProvider(extpoints.EngineOptions{ - Environment: nil, //TODO: Provide something we can use for tests - }) - if err != nil { - t.Fatal("Failed to create Engine: ", err) - } - - // Construct SandboxBuilder - sandboxBuilder, err := engine.NewSandboxBuilder(engines.SandboxOptions{ - TaskContext: nil, // TODO: Create a TaskContext - Payload: parseTestPayload(t, engine, writeVolumePayload), - }) - if err != nil { - t.Fatal("Error creating SandboxBuilder: ", err) - } - - // Make a volume - volume, err := engine.NewCacheFolder() - if err != nil { - t.Fatal("Failed to create a new cache folder: ", err) - } - - // Attach volume to sandbox - err = sandboxBuilder.AttachVolume(mountpoint, volume, false) - if err != nil { - t.Fatal("Failed to attach volume: ", err) - } - - // Start sandbox and wait for result - sandbox, err := sandboxBuilder.StartSandbox() - if err != nil { - t.Fatal("Failed to start sandbox: ", err) - } - // Wait for result - resultSet, err := sandbox.WaitForResult() - if err != nil { - t.Fatal("WaitForResult failed: ", err) - } - - // Check for success - if !resultSet.Success() { - t.Error("Running with writeVolumePayload didn't finish successfully") - } - // Dispose resultSet - err = resultSet.Dispose() - if err != nil { - t.Error("Failed to dispose of ResultSet: ", err) - } - - // Construct SandboxBuilder to assert that something was written to the - // volume, Basically the same as before, but different payload and same - // volume instance. - sb2, err := engine.NewSandboxBuilder(engines.SandboxOptions{ - TaskContext: nil, // TODO: Create a TaskContext - Payload: parseTestPayload(t, engine, assertVolumePayload), - }) - if err != nil { - t.Fatal("Error creating SandboxBuilder: ", err) - } - - // Attach volume to sandbox - err = sb2.AttachVolume(mountpoint, volume, false) - if err != nil { - t.Fatal("Failed to attach volume: ", err) - } - - // Start sandbox and wait for result - s, err := sb2.StartSandbox() - if err != nil { - t.Fatal("Failed to start sandbox: ", err) - } - // Wait for result - rs, err := s.WaitForResult() - if err != nil { - t.Fatal("WaitForResult failed: ", err) - } - - // Check for success - if !rs.Success() { - t.Error("Running with assertVolumePayload didn't finish successfully") - } - // Dispose resultSet - err = rs.Dispose() - if err != nil { - t.Error("Failed to dispose of ResultSet: ", err) - } + }`, } + +func TestVolumeTestCase(t *testing.T) { t.Parallel(); volumeTestCase.Test(t) }