-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move Deploy and Waiter to pubic packages (#662)
- Loading branch information
Showing
13 changed files
with
182 additions
and
197 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package helpers | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"sync/atomic" | ||
"testing" | ||
"time" | ||
|
||
"github.com/matrix-org/complement/b" | ||
"github.com/matrix-org/complement/internal/config" | ||
"github.com/matrix-org/complement/internal/docker" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// TestPackage represents the configuration for a package of tests. A package of tests | ||
// are all tests in the same Go package (directory). | ||
type TestPackage struct { | ||
// the config used for this package. | ||
Config *config.Complement | ||
// the builder we'll use to make containers | ||
complementBuilder *docker.Builder | ||
// a counter to stop tests from allocating the same container name | ||
namespaceCounter uint64 | ||
} | ||
|
||
// NewTestPackage creates a new test package which can be used to deploy containers for all tests | ||
// in a single package. This should be called from `TestMain` which is the Go-provided entry point | ||
// before any tests run. After the tests have run, call `TestPackage.Cleanup`. Tests can deploy | ||
// containers by calling `TestPackage.Deploy`. | ||
func NewTestPackage(pkgNamespace string) (*TestPackage, error) { | ||
cfg := config.NewConfigFromEnvVars(pkgNamespace, "") | ||
log.Printf("config: %+v", cfg) | ||
builder, err := docker.NewBuilder(cfg) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to make docker builder: %w", err) | ||
} | ||
// remove any old images/containers/networks in case we died horribly before | ||
builder.Cleanup() | ||
|
||
// we use GMSL which uses logrus by default. We don't want those logs in our test output unless they are Serious. | ||
logrus.SetLevel(logrus.ErrorLevel) | ||
|
||
return &TestPackage{ | ||
complementBuilder: builder, | ||
namespaceCounter: 0, | ||
Config: cfg, | ||
}, nil | ||
} | ||
|
||
func (tp *TestPackage) Cleanup() { | ||
tp.complementBuilder.Cleanup() | ||
} | ||
|
||
// Deploy will deploy the given blueprint or terminate the test. | ||
// It will construct the blueprint if it doesn't already exist in the docker image cache. | ||
// This function is the main setup function for all tests as it provides a deployment with | ||
// which tests can interact with. | ||
func (tp *TestPackage) Deploy(t *testing.T, blueprint b.Blueprint) *docker.Deployment { | ||
t.Helper() | ||
timeStartBlueprint := time.Now() | ||
if tp.complementBuilder == nil { | ||
t.Fatalf("complementBuilder not set, did you forget to call TestMain?") | ||
} | ||
if err := tp.complementBuilder.ConstructBlueprintIfNotExist(blueprint); err != nil { | ||
t.Fatalf("Deploy: Failed to construct blueprint: %s", err) | ||
} | ||
namespace := fmt.Sprintf("%d", atomic.AddUint64(&tp.namespaceCounter, 1)) | ||
d, err := docker.NewDeployer(namespace, tp.complementBuilder.Config) | ||
if err != nil { | ||
t.Fatalf("Deploy: NewDeployer returned error %s", err) | ||
} | ||
timeStartDeploy := time.Now() | ||
dep, err := d.Deploy(context.Background(), blueprint.Name) | ||
if err != nil { | ||
t.Fatalf("Deploy: Deploy returned error %s", err) | ||
} | ||
t.Logf("Deploy times: %v blueprints, %v containers", timeStartDeploy.Sub(timeStartBlueprint), time.Since(timeStartDeploy)) | ||
return dep | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package helpers | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
"testing" | ||
"time" | ||
) | ||
|
||
// Waiter is a simple primitive to wait for a signal asynchronously. It is preferred | ||
// over other sync primitives due to having more sensible defaults such as built-in timeouts | ||
// if the signal does not appear and ability to signal more than once without panicking. | ||
type Waiter struct { | ||
mu sync.Mutex | ||
ch chan bool | ||
closed bool | ||
} | ||
|
||
// NewWaiter returns a generic struct which can be waited on until `Waiter.Finish` is called. | ||
// A Waiter is similar to a `sync.WaitGroup` of size 1, but without the ability to underflow and | ||
// with built-in timeouts. | ||
func NewWaiter() *Waiter { | ||
return &Waiter{ | ||
ch: make(chan bool), | ||
mu: sync.Mutex{}, | ||
} | ||
} | ||
|
||
// Wait blocks until Finish() is called or until the timeout is reached. | ||
// If the timeout is reached, the test is failed. | ||
func (w *Waiter) Wait(t *testing.T, timeout time.Duration) { | ||
t.Helper() | ||
w.Waitf(t, timeout, "Wait") | ||
} | ||
|
||
// Waitf blocks until Finish() is called or until the timeout is reached. | ||
// If the timeout is reached, the test is failed with the given error message. | ||
func (w *Waiter) Waitf(t *testing.T, timeout time.Duration, errFormat string, args ...interface{}) { | ||
t.Helper() | ||
select { | ||
case <-w.ch: | ||
return | ||
case <-time.After(timeout): | ||
errmsg := fmt.Sprintf(errFormat, args...) | ||
t.Fatalf("%s: timed out after %f seconds.", errmsg, timeout.Seconds()) | ||
} | ||
} | ||
|
||
// Finish will cause all goroutines waiting via Wait to stop waiting and return. | ||
// Once this function has been called, subsequent calls to Wait will return immediately. | ||
// To begin waiting again, make a new Waiter. | ||
func (w *Waiter) Finish() { | ||
w.mu.Lock() | ||
defer w.mu.Unlock() | ||
if w.closed { | ||
return | ||
} | ||
w.closed = true | ||
close(w.ch) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.