Test the processing and upstream connection limits (closes #64)

Failing tests for two limits that do not exist yet. Config:
max_concurrent_processing and upstream_connections, their defaults, and
valid and invalid values from the file and the environment. Image
processor: never more images at once than its limit, waiting and then
failing with ErrTooManyImages when no slot frees, and freeing its slot on
every error. Fetcher: connections to all hosts counted together, apart
from the per-host limit, and freed on errors. Both image routes answer
503 when either wait gives up. TestEnvironmentSetsEveryKey sets the two
new variables, as it compares the whole config. The tests do not compile
until the limits exist.

Model: opus-5-5
This commit is contained in:
2026-09-29 07:32:32 +00:00
parent 0c99be939a
commit 6cb280919f
5 changed files with 645 additions and 0 deletions
@@ -0,0 +1,162 @@
package config
import (
"runtime"
"testing"
)
// The variables that set the two concurrency limits.
const (
testMaxConcurrentProcessingVar = "PIXA_MAX_CONCURRENT_PROCESSING"
testUpstreamConnectionsVar = "PIXA_UPSTREAM_CONNECTIONS"
)
// TestOmittedConcurrencyLimitsUseDefaults checks that an omitted
// max_concurrent_processing is the number of CPUs Go uses and an omitted
// upstream_connections is 64.
func TestOmittedConcurrencyLimitsUseDefaults(t *testing.T) {
t.Parallel()
c, err := configFromYAML(t, signingKeyLine)
if err != nil {
t.Fatalf("minimal config should be valid, got error: %v", err)
}
if c.MaxConcurrentProcessing != runtime.GOMAXPROCS(0) {
t.Errorf("MaxConcurrentProcessing = %d, want %d, one per CPU",
c.MaxConcurrentProcessing, runtime.GOMAXPROCS(0))
}
if c.UpstreamConnections != 64 {
t.Errorf("UpstreamConnections = %d, want 64", c.UpstreamConnections)
}
}
// TestExplicitConcurrencyLimitsAreUsed checks that valid values for the
// two limits are used as given.
func TestExplicitConcurrencyLimitsAreUsed(t *testing.T) {
t.Parallel()
c, err := configFromYAML(t, signingKeyLine+
"max_concurrent_processing: 3\nupstream_connections: 10\n")
if err != nil {
t.Fatalf("valid config should load, got error: %v", err)
}
if c.MaxConcurrentProcessing != 3 {
t.Errorf("MaxConcurrentProcessing = %d, want 3", c.MaxConcurrentProcessing)
}
if c.UpstreamConnections != 10 {
t.Errorf("UpstreamConnections = %d, want 10", c.UpstreamConnections)
}
}
// TestInvalidConcurrencyLimitAbortsStartup checks that a limit that is
// not a whole number of at least 1, or is null, aborts startup naming the
// key and the value, and the variable too where the value could have come
// from it.
func TestInvalidConcurrencyLimitAbortsStartup(t *testing.T) {
t.Parallel()
processing := keyMaxConcurrentProcessing
connections := keyUpstreamConnections
runAbortCases(t, []abortCase{
{
name: "max_concurrent_processing zero",
yaml: signingKeyLine + processing + ": 0\n",
wantErrSubstrings: []string{
processing, testMaxConcurrentProcessingVar, "value 0",
},
},
{
name: "max_concurrent_processing negative",
yaml: signingKeyLine + processing + ": -2\n",
wantErrSubstrings: []string{
processing, testMaxConcurrentProcessingVar, "value -2",
},
},
{
name: "max_concurrent_processing not a number",
yaml: signingKeyLine + processing + ": lots\n",
wantErrSubstrings: []string{
processing, testMaxConcurrentProcessingVar, "lots",
},
},
{
name: "max_concurrent_processing fractional",
yaml: signingKeyLine + processing + ": 1.5\n",
wantErrSubstrings: []string{processing, "1.5"},
},
{
name: "max_concurrent_processing null",
yaml: signingKeyLine + processing + ": null\n",
wantErrSubstrings: []string{processing, nullValueText},
},
{
name: "upstream_connections zero",
yaml: signingKeyLine + connections + ": 0\n",
wantErrSubstrings: []string{
connections, testUpstreamConnectionsVar, "value 0",
},
},
{
name: "upstream_connections negative",
yaml: signingKeyLine + connections + ": -5\n",
wantErrSubstrings: []string{
connections, testUpstreamConnectionsVar, "value -5",
},
},
{
name: "upstream_connections not a number",
yaml: signingKeyLine + connections + ": many\n",
wantErrSubstrings: []string{
connections, testUpstreamConnectionsVar, "many",
},
},
{
name: "upstream_connections null",
yaml: signingKeyLine + connections + ": null\n",
wantErrSubstrings: []string{connections, nullValueText},
},
})
}
// TestConcurrencyLimitsFromEnvironment checks that the two variables set
// the limits over the config file, and that an invalid value in either
// aborts startup naming the variable and the value.
func TestConcurrencyLimitsFromEnvironment(t *testing.T) {
t.Setenv(testMaxConcurrentProcessingVar, "3")
t.Setenv(testUpstreamConnectionsVar, "10")
c, err := configFromYAML(t, signingKeyLine+
"max_concurrent_processing: 5\nupstream_connections: 50\n")
if err != nil {
t.Fatalf("limits from the environment should load: %v", err)
}
if c.MaxConcurrentProcessing != 3 || c.UpstreamConnections != 10 {
t.Errorf("limits = %d and %d, want 3 and 10 from the environment",
c.MaxConcurrentProcessing, c.UpstreamConnections)
}
cases := []struct {
variable string
value string
}{
{testMaxConcurrentProcessingVar, "lots"},
{testMaxConcurrentProcessingVar, "0"},
{testUpstreamConnectionsVar, "-1"},
{testUpstreamConnectionsVar, "ten"},
}
for _, tc := range cases {
t.Run(tc.variable+"="+tc.value, func(t *testing.T) {
t.Setenv(tc.variable, tc.value)
_, err := configFromYAML(t, signingKeyLine)
wantStartupError(t, err, tc.variable, tc.value)
})
}
}
+4
View File
@@ -67,6 +67,8 @@ func TestEnvironmentSetsEveryKey(t *testing.T) {
t.Setenv("PIXA_ALLOWLIST_HOSTS", "s3.sneak.cloud,.example.com")
t.Setenv("PIXA_ALLOW_HTTP", "true")
t.Setenv("PIXA_UPSTREAM_CONNECTIONS_PER_HOST", "5")
t.Setenv("PIXA_UPSTREAM_CONNECTIONS", "10")
t.Setenv("PIXA_MAX_CONCURRENT_PROCESSING", "3")
t.Setenv("PIXA_CACHE_MAX_BYTES", "1024")
t.Setenv("PIXA_BLOCKED_NETWORKS", "203.0.113.0/24")
t.Setenv("PIXA_TRUSTED_PROXIES", "192.0.2.0/24")
@@ -93,6 +95,8 @@ func TestEnvironmentSetsEveryKey(t *testing.T) {
AllowlistHosts: []string{testHostS3, ".example.com"},
AllowHTTP: true,
UpstreamConnectionsPerHost: 5,
UpstreamConnections: 10,
MaxConcurrentProcessing: 3,
CacheMaxBytes: 1024,
cacheMaxBytesExplicit: true,
BlockedNetworks: []netip.Prefix{netip.MustParsePrefix("203.0.113.0/24")},