This commit is contained in:
a 2024-06-18 18:16:33 -05:00
parent 8e0d3e1ec5
commit 93a1853022
No known key found for this signature in database
GPG key ID: 374BC539FE795AF0
6 changed files with 99 additions and 68 deletions

View file

@ -20,6 +20,7 @@ import (
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"io/fs"
@ -778,7 +779,10 @@ func exitProcess(ctx context.Context, logger *zap.Logger) {
} else {
logger.Error("unclean shutdown")
}
os.Exit(exitCode)
// check if we are in test environment, and dont call exit if we are
if flag.Lookup("test.v") == nil || strings.Contains(os.Args[0], ".test") {
os.Exit(exitCode)
}
}()
if remoteAdminServer != nil {

View file

@ -81,6 +81,10 @@ func NewTester(t testing.TB) *Tester {
}
}
func (t *Tester) T() testing.TB {
return t.t
}
type configLoadError struct {
Response string
}
@ -92,33 +96,37 @@ func timeElapsed(start time.Time, name string) {
log.Printf("%s took %s", name, elapsed)
}
// InitServer this will configure the server with a configurion of a specific
// type. The configType must be either "json" or the adapter type.
func (tc *Tester) InitServer(rawConfig string, configType string) {
if err := tc.initServer(rawConfig, configType); err != nil {
tc.t.Logf("failed to load config: %s", err)
// launch caddy will start the server
func (tc *Tester) LaunchCaddy() {
if err := tc.startServer(); err != nil {
tc.t.Logf("failed to start server: %s", err)
tc.t.Fail()
}
if err := tc.ensureConfigRunning(rawConfig, configType); err != nil {
}
// DEPRECATED: InitServer
// Initserver is shorthand for LaunchCaddy() and LoadConfig(rawConfig, configType string)
func (tc *Tester) InitServer(rawConfig string, configType string) {
if err := tc.startServer(); err != nil {
tc.t.Logf("failed to start server: %s", err)
tc.t.Fail()
}
if err := tc.LoadConfig(rawConfig, configType); err != nil {
tc.t.Logf("failed ensuring config is running: %s", err)
tc.t.Fail()
}
}
// InitServer this will configure the server with a configurion of a specific
// type. The configType must be either "json" or the adapter type.
func (tc *Tester) initServer(rawConfig string, configType string) error {
func (tc *Tester) startServer() error {
if testing.Short() {
tc.t.SkipNow()
return nil
}
err := validateTestPrerequisites(tc.t)
err := validateAndStartServer(tc.t)
if err != nil {
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
return nil
}
tc.t.Cleanup(func() {
if tc.t.Failed() && tc.configLoaded {
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
@ -133,8 +141,35 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
_ = json.Indent(&out, body, "", " ")
tc.t.Logf("----------- failed with config -----------\n%s", out.String())
}
})
// now shutdown the server, since the test is done.
_, err := http.Post(fmt.Sprintf("http://localhost:%d/stop", Default.AdminPort), "", nil)
if err != nil {
tc.t.Log("couldn't stop admin server")
}
time.Sleep(1 * time.Millisecond)
// try ensure the admin api is stopped three times.
for retries := 0; retries < 3; retries++ {
if isCaddyAdminRunning() != nil {
return
}
time.Sleep(10 * time.Millisecond)
tc.t.Log("timed out waiting for admin server to stop")
}
})
return nil
}
func (tc *Tester) MustLoadConfig(rawConfig string, configType string) {
if err := tc.LoadConfig(rawConfig, configType); err != nil {
tc.t.Logf("failed ensuring config is running: %s", err)
tc.t.Fail()
}
}
// LoadConfig loads the config to the tester server and also ensures that the config was loaded
func (tc *Tester) LoadConfig(rawConfig string, configType string) error {
originalRawConfig := rawConfig
rawConfig = prependCaddyFilePath(rawConfig)
// normalize JSON config
if configType == "json" {
@ -185,7 +220,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
}
tc.configLoaded = true
return nil
return tc.ensureConfigRunning(originalRawConfig, configType)
}
func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error {
@ -226,7 +261,9 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
return actual
}
for retries := 10; retries > 0; retries-- {
// TODO: does this really need to be tried more than once?
// Caddy should block until the new config is loaded, which means needing to wait is a caddy bug
for retries := 3; retries > 0; retries-- {
if reflect.DeepEqual(expected, fetchConfig(client)) {
return nil
}
@ -241,9 +278,9 @@ const initConfig = `{
}
`
// validateTestPrerequisites ensures the certificates are available in the
// designated path and Caddy sub-process is running.
func validateTestPrerequisites(t testing.TB) error {
// validateAndStartServer ensures the certificates are available in the
// designated path, launches caddy, and then ensures the Caddy sub-process is running.
func validateAndStartServer(t testing.TB) error {
// check certificates are found
for _, certName := range Default.Certificates {
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) {
@ -269,9 +306,8 @@ func validateTestPrerequisites(t testing.TB) error {
go func() {
caddycmd.Main()
}()
// wait for caddy to start serving the initial config
for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
// wait for caddy admin api to start. it should happen quickly.
for retries := 3; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
time.Sleep(1 * time.Second)
}
}
@ -342,8 +378,8 @@ func CreateTestingTransport() *http.Transport {
// AssertLoadError will load a config and expect an error
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
tc := NewTester(t)
err := tc.initServer(rawConfig, configType)
tc.LaunchCaddy()
err := tc.LoadConfig(rawConfig, configType)
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
}

View file

@ -19,19 +19,27 @@ import (
"go.uber.org/zap"
)
func curry2[A, B any](fn func(A, B)) func(a A) func(b B) {
return func(a A) func(B) {
return func(b B) {
fn(a, b)
}
}
}
const acmeChallengePort = 9081
// Test the basic functionality of Caddy's ACME server
func TestACMEServerWithDefaults(t *testing.T) {
ctx := context.Background()
logger, err := zap.NewDevelopment()
if err != nil {
t.Error(err)
return
}
func TestAcmeServer(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
tester.LaunchCaddy()
t.Run("WithDefaults", curry2(testACMEServerWithDefaults)(tester))
t.Run("WithMismatchedChallenges", curry2(testACMEServerWithDefaults)(tester))
}
// Test the basic functionality of Caddy's ACME server
func testACMEServerWithDefaults(tester *caddytest.Tester, t *testing.T) {
ctx := context.Background()
tester.MustLoadConfig(`
{
skip_install_trust
admin localhost:2999
@ -44,6 +52,7 @@ func TestACMEServerWithDefaults(t *testing.T) {
}
`, "caddyfile")
logger := caddy.Log().Named("acmeserver")
client := acmez.Client{
Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory",
@ -93,20 +102,10 @@ func TestACMEServerWithDefaults(t *testing.T) {
}
}
func TestACMEServerWithMismatchedChallenges(t *testing.T) {
func testACMEServerWithMismatchedChallenges(tester *caddytest.Tester, t *testing.T) {
ctx := context.Background()
logger := caddy.Log().Named("acmez")
tester := caddytest.NewTester(t)
tester.InitServer(`
{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
local_certs
}
acme.localhost {
tester.MustLoadConfig(`
acme_server {
challenges tls-alpn-01
}

View file

@ -8,10 +8,10 @@ import (
"strings"
"testing"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
"go.uber.org/zap"
)
func TestACMEServerDirectory(t *testing.T) {
@ -66,11 +66,7 @@ func TestACMEServerAllowPolicy(t *testing.T) {
`, "caddyfile")
ctx := context.Background()
logger, err := zap.NewDevelopment()
if err != nil {
t.Error(err)
return
}
logger := caddy.Log().Named("acmez")
client := acmez.Client{
Client: &acme.Client{
@ -155,11 +151,7 @@ func TestACMEServerDenyPolicy(t *testing.T) {
`, "caddyfile")
ctx := context.Background()
logger, err := zap.NewDevelopment()
if err != nil {
t.Error(err)
return
}
logger := caddy.Log().Named("acmez")
client := acmez.Client{
Client: &acme.Client{
@ -197,7 +189,7 @@ func TestACMEServerDenyPolicy(t *testing.T) {
_, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"deny.localhost"})
if err == nil {
t.Errorf("obtaining certificate for 'deny.localhost' domain")
} else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
} else if !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
t.Logf("unexpected error: %v", err)
}
}

View file

@ -21,7 +21,7 @@ import (
// (see https://github.com/caddyserver/caddy/issues/3556 for use case)
func TestH2ToH2CStream(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
tester.InitServer(`
{
"admin": {
"listen": "localhost:2999"
@ -205,18 +205,11 @@ func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
// (see https://github.com/caddyserver/caddy/issues/3606 for use case)
func TestH2ToH1ChunkedResponse(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
tester.InitServer(`
{
"admin": {
"listen": "localhost:2999"
},
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
},
"apps": {
"http": {
"http_port": 9080,

View file

@ -16,6 +16,7 @@ package caddy
import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
@ -699,7 +700,13 @@ type defaultCustomLog struct {
// and enables INFO-level logs and higher.
func newDefaultProductionLog() (*defaultCustomLog, error) {
cl := new(CustomLog)
cl.writerOpener = StderrWriter{}
f := flag.Lookup("test.v")
if (f != nil && f.Value.String() != "true") || strings.Contains(os.Args[0], ".test") {
cl.writerOpener = &DiscardWriter{}
} else {
cl.writerOpener = StderrWriter{}
}
var err error
cl.writer, err = cl.writerOpener.OpenWriter()
if err != nil {