diff --git a/testcontainer.go b/testcontainer.go index 6c42b00d42..3410e06b80 100644 --- a/testcontainer.go +++ b/testcontainer.go @@ -1 +1,28 @@ package testcontainers + +import "github.com/testcontainers/testcontainers-go/internal/testcontainerssession" + +// SessionID returns a unique session ID for the current test session. Because each Go package +// will be run in a separate process, we need a way to identify the current test session. +// By test session, we mean: +// - a single "go test" invocation (including flags) +// - a single "go test ./..." invocation (including flags) +// - the execution of a single test or a set of tests using the IDE +// +// As a consequence, with the sole goal of aggregating test execution across multiple +// packages, this variable will contain the value of the parent process ID (pid) of the current process +// and its creation date, to use it to generate a unique session ID. We are using the parent pid because +// the current process will be a child process of: +// - the process that is running the tests, e.g.: "go test"; +// - the process that is running the application in development mode, e.g. "go run main.go -tags dev"; +// - the process that is running the tests in the IDE, e.g.: "go test ./...". +// +// Finally, we will hash the combination of the "testcontainers-go:" string with the parent pid +// and the creation date of that parent process to generate a unique session ID. +// +// This sessionID will be used to: +// - identify the test session, aggregating the test execution of multiple packages in the same test session. +// - tag the containers created by testcontainers-go, adding a label to the container with the session ID. +func SessionID() string { + return testcontainerssession.SessionID() +} diff --git a/testcontainers_test.go b/testcontainers_test.go new file mode 100644 index 0000000000..fe5af71e89 --- /dev/null +++ b/testcontainers_test.go @@ -0,0 +1,85 @@ +package testcontainers + +import ( + "fmt" + "os" + "os/exec" + "regexp" + "testing" +) + +func TestSessionID(t *testing.T) { + t.Run("SessionID() returns a non-empty string", func(t *testing.T) { + sessionID := SessionID() + if sessionID == "" { + t.Error("SessionID() returned an empty string") + } + }) + + t.Run("Multiple calls to SessionID() return the same value", func(t *testing.T) { + sessionID1 := SessionID() + sessionID2 := SessionID() + if sessionID1 != sessionID2 { + t.Errorf("SessionID() returned different values: %s != %s", sessionID1, sessionID2) + } + }) + + t.Run("Multiple calls to SessionID() in multiple goroutines return the same value", func(t *testing.T) { + sessionID1 := "" + sessionID2 := "" + + done := make(chan bool) + go func() { + sessionID1 = SessionID() + done <- true + }() + + go func() { + sessionID2 = SessionID() + done <- true + }() + + <-done + <-done + + if sessionID1 != sessionID2 { + t.Errorf("SessionID() returned different values: %s != %s", sessionID1, sessionID2) + } + }) + + t.Run("SessionID() from different child processes returns the same value", func(t *testing.T) { + args := []string{"test", "./...", "-v", "-run", "TestSessionIDHelper"} + env := append(os.Environ(), "TESTCONTAINERS_SESSION_ID_HELPER=1") + + re := regexp.MustCompile(">>>(.*)<<<") + + cmd1 := exec.Command("go", args...) + cmd1.Env = env + stdoutStderr1, err := cmd1.CombinedOutput() + if err != nil { + t.Errorf("cmd1.Run() failed with %s", err) + } + sessionID1 := re.FindString(string(stdoutStderr1)) + + cmd2 := exec.Command("go", args...) + cmd2.Env = env + stdoutStderr2, err := cmd2.CombinedOutput() + if err != nil { + t.Errorf("cmd2.Run() failed with %s", err) + } + sessionID2 := re.FindString(string(stdoutStderr2)) + + if sessionID1 != sessionID2 { + t.Errorf("SessionID() returned different values: %s != %s", sessionID1, sessionID2) + } + }) +} + +// Not a real test, used to print out the session ID +func TestSessionIDHelper(t *testing.T) { + if os.Getenv("TESTCONTAINERS_SESSION_ID_HELPER") == "" { + t.Skip("Not a real test, used as a test helper") + } + + fmt.Printf(">>>%s<<<\n", SessionID()) +}