-
-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
*: Add integration tests for examples
- Loading branch information
1 parent
e7c0290
commit 415069b
Showing
1 changed file
with
140 additions
and
0 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,140 @@ | ||
package examples_test | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"io" | ||
"os" | ||
"os/exec" | ||
"testing" | ||
"time" | ||
|
||
"github.com/diamondburned/arikawa/v3/internal/testenv" | ||
) | ||
|
||
func TestExamples(t *testing.T) { | ||
// Assert that the tests only run when the environment variables are set. | ||
testenv.Must(t) | ||
|
||
// Assert that the Go compiler is available. | ||
_, err := exec.LookPath("go") | ||
if err != nil { | ||
t.Skip("skipping test; go compiler not found") | ||
} | ||
|
||
examplePackages, err := os.ReadDir(".") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Run all examples for 10 seconds each. | ||
// | ||
// TODO(diamondburned): find a way to detect that the bot is online. Maybe | ||
// force all examples to print the current username? | ||
const exampleRunDuration = 10 * time.Second | ||
|
||
buildDir, err := os.MkdirTemp("", "arikawa-examples") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
t.Cleanup(func() { | ||
if err := os.RemoveAll(buildDir); err != nil { | ||
t.Log("cannot remove artifacts dir:", err) | ||
} | ||
}) | ||
|
||
for _, pkg := range examplePackages { | ||
if !pkg.IsDir() { | ||
continue | ||
} | ||
|
||
// Assert package main. | ||
if _, err := os.Stat(pkg.Name() + "/main.go"); err != nil { | ||
continue | ||
} | ||
|
||
pkg := pkg | ||
t.Run(pkg.Name(), func(t *testing.T) { | ||
t.Parallel() | ||
|
||
binPath := buildDir + "/" + pkg.Name() | ||
|
||
gobuild := exec.Command("go", "build", "-o", binPath, "./"+pkg.Name()) | ||
gobuild.Stderr = &lineLogger{dst: func(line string) { t.Log("go build:", line) }} | ||
if err := gobuild.Run(); err != nil { | ||
t.Fatal("cannot go build:", err) | ||
} | ||
|
||
timer := time.NewTimer(exampleRunDuration) | ||
t.Cleanup(func() { timer.Stop() }) | ||
|
||
bin := exec.Command(binPath) | ||
bin.Stderr = &lineLogger{dst: func(line string) { t.Log(pkg.Name()+":", line) }} | ||
if err := bin.Start(); err != nil { | ||
t.Fatal("cannot start binary:", err) | ||
} | ||
|
||
cmdDone := make(chan struct{}) | ||
go func() { | ||
defer close(cmdDone) | ||
|
||
err := bin.Wait() | ||
if err == nil { | ||
return // all good | ||
} | ||
|
||
var exitErr *exec.ExitError | ||
if !errors.As(err, &exitErr) || !exitErr.Exited() { | ||
return | ||
} | ||
|
||
t.Error("binary exited with status", exitErr.ExitCode()) | ||
}() | ||
|
||
select { | ||
case <-cmdDone: | ||
return | ||
case <-timer.C: | ||
} | ||
|
||
// Works well. Just exit. | ||
if err := bin.Process.Signal(os.Interrupt); err != nil { | ||
t.Log("cannot interrupt binary:", err) | ||
bin.Process.Kill() | ||
} | ||
|
||
exitTimer := time.NewTimer(5 * time.Second) | ||
t.Cleanup(func() { exitTimer.Stop() }) | ||
|
||
select { | ||
case <-cmdDone: | ||
return | ||
case <-exitTimer.C: | ||
t.Error("example did not exit after 5 seconds") | ||
bin.Process.Kill() | ||
} | ||
}) | ||
} | ||
} | ||
|
||
type lineLogger struct { | ||
dst func(string) | ||
buf bytes.Buffer | ||
} | ||
|
||
func (l *lineLogger) Write(p []byte) (n int, err error) { | ||
n, _ = l.buf.Write(p) | ||
for { | ||
line, err := l.buf.ReadString('\n') | ||
if err != nil { | ||
if err == io.EOF { | ||
break | ||
} | ||
return n, err | ||
} | ||
line = line[:len(line)-1] // remove newline | ||
l.dst(line) | ||
} | ||
return n, nil | ||
} |