git » chasquid » commit 7909b47

config: Tidy default handling and comparisons in tests

author Alberto Bertogli
2020-05-16 22:48:09 UTC
committer Alberto Bertogli
2020-05-16 22:48:09 UTC
parent b1fe4f81f9a76315706e5d1fc682dfbf2ca0dd0c

config: Tidy default handling and comparisons in tests

This patch tidies how defaults are handled in the config, using a new
logic to allow "overriding" one config (the default) with another (the
user supplied).

It also improves how the comparisons are done in the tests, using the
more convenient "github.com/google/go-cmp/cmp" package, which also
prints nice diffs on errors.

This is in preparation for a future path where the override mechanism
will be reused.

go.mod +1 -0
internal/config/config.go +68 -29
internal/config/config_test.go +32 -32

diff --git a/go.mod b/go.mod
index f3ec22e..0f69407 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ require (
 	blitiri.com.ar/go/systemd v0.0.0-20171003041308-cdc4fd023aa4
 	github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
 	github.com/golang/protobuf v1.4.0
+	github.com/google/go-cmp v0.4.0
 	golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
 	golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
 	golang.org/x/text v0.3.2
diff --git a/internal/config/config.go b/internal/config/config.go
index 9a01d91..bb0564d 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -12,24 +12,47 @@ import (
 	"blitiri.com.ar/go/log"
 
 	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/proto"
 )
 
+var defaultConfig = &Config{
+	MaxDataSizeMb: 50,
+
+	SmtpAddress:              []string{"systemd"},
+	SubmissionAddress:        []string{"systemd"},
+	SubmissionOverTlsAddress: []string{"systemd"},
+
+	MailDeliveryAgentBin:  "maildrop",
+	MailDeliveryAgentArgs: []string{"-f", "%from%", "-d", "%to_user%"},
+
+	DataDir: "/var/lib/chasquid",
+
+	SuffixSeparators: "+",
+	DropCharacters:   ".",
+
+	MailLogPath: "<syslog>",
+}
+
 // Load the config from the given file.
 func Load(path string) (*Config, error) {
-	c := &Config{}
+	// Start with a copy of the default config.
+	c := proto.Clone(defaultConfig).(*Config)
 
+	// Load from the path.
 	buf, err := ioutil.ReadFile(path)
 	if err != nil {
 		return nil, fmt.Errorf("failed to read config at %q: %v", path, err)
 	}
 
-	err = prototext.Unmarshal(buf, c)
+	fromFile := &Config{}
+	err = prototext.Unmarshal(buf, fromFile)
 	if err != nil {
 		return nil, fmt.Errorf("parsing config: %v", err)
 	}
+	override(c, fromFile)
 
-	// Fill in defaults for anything that's missing.
-
+	// Handle hostname separate, because if it is set, we don't need to call
+	// os.Hostname which can fail.
 	if c.Hostname == "" {
 		c.Hostname, err = os.Hostname()
 		if err != nil {
@@ -37,45 +60,61 @@ func Load(path string) (*Config, error) {
 		}
 	}
 
-	if c.MaxDataSizeMb == 0 {
-		c.MaxDataSizeMb = 50
-	}
+	return c, nil
+}
 
-	if len(c.SmtpAddress) == 0 {
-		c.SmtpAddress = append(c.SmtpAddress, "systemd")
+// Override fields in `c` that are set in `o`. We can't use proto.Merge
+// because the semantics would not be convenient for overriding.
+func override(c, o *Config) {
+	if o.Hostname != "" {
+		c.Hostname = o.Hostname
 	}
-	if len(c.SubmissionAddress) == 0 {
-		c.SubmissionAddress = append(c.SubmissionAddress, "systemd")
+	if o.MaxDataSizeMb > 0 {
+		c.MaxDataSizeMb = o.MaxDataSizeMb
 	}
-	if len(c.SubmissionOverTlsAddress) == 0 {
-		c.SubmissionOverTlsAddress = append(c.SubmissionOverTlsAddress, "systemd")
+	if len(o.SmtpAddress) > 0 {
+		c.SmtpAddress = o.SmtpAddress
 	}
-
-	if c.MailDeliveryAgentBin == "" {
-		c.MailDeliveryAgentBin = "maildrop"
+	if len(o.SubmissionAddress) > 0 {
+		c.SubmissionAddress = o.SubmissionAddress
 	}
-	if len(c.MailDeliveryAgentArgs) == 0 {
-		c.MailDeliveryAgentArgs = append(c.MailDeliveryAgentArgs,
-			"-f", "%from%", "-d", "%to_user%")
+	if len(o.SubmissionOverTlsAddress) > 0 {
+		c.SubmissionOverTlsAddress = o.SubmissionOverTlsAddress
 	}
-
-	if c.DataDir == "" {
-		c.DataDir = "/var/lib/chasquid"
+	if o.MonitoringAddress != "" {
+		c.MonitoringAddress = o.MonitoringAddress
 	}
 
-	if c.SuffixSeparators == "" {
-		c.SuffixSeparators = "+"
+	if o.MailDeliveryAgentBin != "" {
+		c.MailDeliveryAgentBin = o.MailDeliveryAgentBin
+	}
+	if len(o.MailDeliveryAgentArgs) > 0 {
+		c.MailDeliveryAgentArgs = o.MailDeliveryAgentArgs
 	}
 
-	if c.DropCharacters == "" {
-		c.DropCharacters = "."
+	if o.DataDir != "" {
+		c.DataDir = o.DataDir
 	}
 
-	if c.MailLogPath == "" {
-		c.MailLogPath = "<syslog>"
+	if o.SuffixSeparators != "" {
+		c.SuffixSeparators = o.SuffixSeparators
+	}
+	if o.DropCharacters != "" {
+		c.DropCharacters = o.DropCharacters
+	}
+	if o.MailLogPath != "" {
+		c.MailLogPath = o.MailLogPath
 	}
 
-	return c, nil
+	if o.DovecotAuth {
+		c.DovecotAuth = true
+	}
+	if o.DovecotUserdbPath != "" {
+		c.DovecotUserdbPath = o.DovecotUserdbPath
+	}
+	if o.DovecotClientPath != "" {
+		c.DovecotClientPath = o.DovecotClientPath
+	}
 }
 
 // LogConfig logs the given configuration, in a human-friendly way.
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 7b6dfc1..b0e54c2 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -8,6 +8,9 @@ import (
 
 	"blitiri.com.ar/go/chasquid/internal/testlib"
 	"blitiri.com.ar/go/log"
+	"github.com/google/go-cmp/cmp"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/testing/protocmp"
 )
 
 func mustCreateConfig(t *testing.T, contents string) (string, string) {
@@ -30,26 +33,13 @@ func TestEmptyConfig(t *testing.T) {
 	}
 
 	// Test the default values are set.
-
+	defaults := proto.Clone(defaultConfig).(*Config)
 	hostname, _ := os.Hostname()
-	if c.Hostname == "" || c.Hostname != hostname {
-		t.Errorf("invalid hostname %q, should be: %q", c.Hostname, hostname)
-	}
-
-	if c.MaxDataSizeMb != 50 {
-		t.Errorf("max data size != 50: %d", c.MaxDataSizeMb)
-	}
-
-	if len(c.SmtpAddress) != 1 || c.SmtpAddress[0] != "systemd" {
-		t.Errorf("unexpected address default: %v", c.SmtpAddress)
-	}
-
-	if len(c.SubmissionAddress) != 1 || c.SubmissionAddress[0] != "systemd" {
-		t.Errorf("unexpected address default: %v", c.SubmissionAddress)
-	}
+	defaults.Hostname = hostname
 
-	if c.MonitoringAddress != "" {
-		t.Errorf("monitoring address is set: %v", c.MonitoringAddress)
+	diff := cmp.Diff(defaults, c, protocmp.Transform())
+	if diff != "" {
+		t.Errorf("Load() mismatch (-want +got):\n%s", diff)
 	}
 
 	testLogConfig(c)
@@ -60,6 +50,8 @@ func TestFullConfig(t *testing.T) {
 		hostname: "joust"
 		smtp_address: ":1234"
 		smtp_address: ":5678"
+		submission_address: ":10001"
+		submission_address: ":10002"
 		monitoring_address: ":1111"
 		max_data_size_mb: 26
 	`
@@ -67,26 +59,34 @@ func TestFullConfig(t *testing.T) {
 	tmpDir, path := mustCreateConfig(t, confStr)
 	defer testlib.RemoveIfOk(t, tmpDir)
 
-	c, err := Load(path)
-	if err != nil {
-		t.Fatalf("error loading non-existent config: %v", err)
-	}
+	expected := &Config{
+		Hostname:      "joust",
+		MaxDataSizeMb: 26,
 
-	if c.Hostname != "joust" {
-		t.Errorf("hostname %q != 'joust'", c.Hostname)
-	}
+		SmtpAddress:              []string{":1234", ":5678"},
+		SubmissionAddress:        []string{":10001", ":10002"},
+		SubmissionOverTlsAddress: []string{"systemd"},
+		MonitoringAddress:        ":1111",
+
+		MailDeliveryAgentBin:  "maildrop",
+		MailDeliveryAgentArgs: []string{"-f", "%from%", "-d", "%to_user%"},
+
+		DataDir: "/var/lib/chasquid",
 
-	if c.MaxDataSizeMb != 26 {
-		t.Errorf("max data size != 26: %d", c.MaxDataSizeMb)
+		SuffixSeparators: "+",
+		DropCharacters:   ".",
+
+		MailLogPath: "<syslog>",
 	}
 
-	if len(c.SmtpAddress) != 2 ||
-		c.SmtpAddress[0] != ":1234" || c.SmtpAddress[1] != ":5678" {
-		t.Errorf("different address: %v", c.SmtpAddress)
+	c, err := Load(path)
+	if err != nil {
+		t.Fatalf("error loading non-existent config: %v", err)
 	}
 
-	if c.MonitoringAddress != ":1111" {
-		t.Errorf("monitoring address %q != ':1111;", c.MonitoringAddress)
+	diff := cmp.Diff(expected, c, protocmp.Transform())
+	if diff != "" {
+		t.Errorf("Load() mismatch (-want +got):\n%s", diff)
 	}
 
 	testLogConfig(c)