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/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"flag"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@ -778,7 +779,10 @@ func exitProcess(ctx context.Context, logger *zap.Logger) {
} else { } else {
logger.Error("unclean shutdown") 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 { 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 { type configLoadError struct {
Response string Response string
} }
@ -92,33 +96,37 @@ func timeElapsed(start time.Time, name string) {
log.Printf("%s took %s", name, elapsed) log.Printf("%s took %s", name, elapsed)
} }
// InitServer this will configure the server with a configurion of a specific // launch caddy will start the server
// type. The configType must be either "json" or the adapter type. func (tc *Tester) LaunchCaddy() {
func (tc *Tester) InitServer(rawConfig string, configType string) { if err := tc.startServer(); err != nil {
if err := tc.initServer(rawConfig, configType); err != nil { tc.t.Logf("failed to start server: %s", err)
tc.t.Logf("failed to load config: %s", err)
tc.t.Fail() 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.Logf("failed ensuring config is running: %s", err)
tc.t.Fail() tc.t.Fail()
} }
} }
// InitServer this will configure the server with a configurion of a specific func (tc *Tester) startServer() error {
// type. The configType must be either "json" or the adapter type.
func (tc *Tester) initServer(rawConfig string, configType string) error {
if testing.Short() { if testing.Short() {
tc.t.SkipNow() tc.t.SkipNow()
return nil return nil
} }
err := validateAndStartServer(tc.t)
err := validateTestPrerequisites(tc.t)
if err != nil { if err != nil {
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err) tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
return nil return nil
} }
tc.t.Cleanup(func() { tc.t.Cleanup(func() {
if tc.t.Failed() && tc.configLoaded { if tc.t.Failed() && tc.configLoaded {
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort)) 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, "", " ") _ = json.Indent(&out, body, "", " ")
tc.t.Logf("----------- failed with config -----------\n%s", out.String()) 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) rawConfig = prependCaddyFilePath(rawConfig)
// normalize JSON config // normalize JSON config
if configType == "json" { if configType == "json" {
@ -185,7 +220,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
} }
tc.configLoaded = true tc.configLoaded = true
return nil return tc.ensureConfigRunning(originalRawConfig, configType)
} }
func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error { func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error {
@ -226,7 +261,9 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
return actual 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)) { if reflect.DeepEqual(expected, fetchConfig(client)) {
return nil return nil
} }
@ -241,9 +278,9 @@ const initConfig = `{
} }
` `
// validateTestPrerequisites ensures the certificates are available in the // validateAndStartServer ensures the certificates are available in the
// designated path and Caddy sub-process is running. // designated path, launches caddy, and then ensures the Caddy sub-process is running.
func validateTestPrerequisites(t testing.TB) error { func validateAndStartServer(t testing.TB) error {
// check certificates are found // check certificates are found
for _, certName := range Default.Certificates { for _, certName := range Default.Certificates {
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) { if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) {
@ -269,9 +306,8 @@ func validateTestPrerequisites(t testing.TB) error {
go func() { go func() {
caddycmd.Main() caddycmd.Main()
}() }()
// wait for caddy admin api to start. it should happen quickly.
// wait for caddy to start serving the initial config for retries := 3; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
} }
@ -342,8 +378,8 @@ func CreateTestingTransport() *http.Transport {
// AssertLoadError will load a config and expect an error // AssertLoadError will load a config and expect an error
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) { func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
tc := NewTester(t) tc := NewTester(t)
tc.LaunchCaddy()
err := tc.initServer(rawConfig, configType) err := tc.LoadConfig(rawConfig, configType)
if !strings.Contains(err.Error(), expectedError) { if !strings.Contains(err.Error(), expectedError) {
t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error()) t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
} }

View file

@ -19,19 +19,27 @@ import (
"go.uber.org/zap" "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 const acmeChallengePort = 9081
// Test the basic functionality of Caddy's ACME server func TestAcmeServer(t *testing.T) {
func TestACMEServerWithDefaults(t *testing.T) {
ctx := context.Background()
logger, err := zap.NewDevelopment()
if err != nil {
t.Error(err)
return
}
tester := caddytest.NewTester(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 skip_install_trust
admin localhost:2999 admin localhost:2999
@ -44,6 +52,7 @@ func TestACMEServerWithDefaults(t *testing.T) {
} }
`, "caddyfile") `, "caddyfile")
logger := caddy.Log().Named("acmeserver")
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", 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() ctx := context.Background()
logger := caddy.Log().Named("acmez") logger := caddy.Log().Named("acmez")
tester.MustLoadConfig(`
tester := caddytest.NewTester(t)
tester.InitServer(`
{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
local_certs
}
acme.localhost {
acme_server { acme_server {
challenges tls-alpn-01 challenges tls-alpn-01
} }

View file

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

View file

@ -210,13 +210,6 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"
}, },
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
},
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": 9080,

View file

@ -16,6 +16,7 @@ package caddy
import ( import (
"encoding/json" "encoding/json"
"flag"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -699,7 +700,13 @@ type defaultCustomLog struct {
// and enables INFO-level logs and higher. // and enables INFO-level logs and higher.
func newDefaultProductionLog() (*defaultCustomLog, error) { func newDefaultProductionLog() (*defaultCustomLog, error) {
cl := new(CustomLog) 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 var err error
cl.writer, err = cl.writerOpener.OpenWriter() cl.writer, err = cl.writerOpener.OpenWriter()
if err != nil { if err != nil {