Repository: inbucket/inbucket
Pull request #501 (2024-02-19 19:25:20)

build(deps): bump docker/setup-qemu-action from 2 to 3
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/docker/setup-qemu-action/releases">docker/setup-qemu-action's releases</a>.</em></p>
<blockquote>
<h2>v3.0.0</h2>
<ul>
<li>Node 20 as default runtime (requires <a href="https://github.com/actions/runner/releases/tag/v2.308.0">Actions Runner v2.308.0</a> or later) by <a href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/102">docker/setup-qemu-action#102</a></li>
<li>Bump <code>@​actions/core</code> from 1.10.0 to 1.10.1 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/103">docker/setup-qemu-action#103</a></li>
<li>Bump semver from 6.3.0 to 6.3.1 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/89">docker/setup-qemu-action#89</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/docker/setup-qemu-action/compare/v2.2.0...v3.0.0">https://github.com/docker/setup-qemu-action/compare/v2.2.0...v3.0.0</a></p>
<h2>v2.2.0</h2>
<ul>
<li>Trim off spaces in <code>platforms</code> input by <a href="https://github.com/Chocobo1"><code>@​Chocobo1</code></a> in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/64">docker/setup-qemu-action#64</a></li>
<li>Switch to actions-toolkit implementation by <a href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/70">docker/setup-qemu-action#70</a> <a href="https://redirect.github.com/docker/setup-qemu-action/pull/80">docker/setup-qemu-action#80</a> <a href="https://redirect.github.com/docker/setup-qemu-action/pull/83">docker/setup-qemu-action#83</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/docker/setup-qemu-action/compare/v2.1.0...v2.2.0">https://github.com/docker/setup-qemu-action/compare/v2.1.0...v2.2.0</a></p>
<h2>v2.1.0</h2>
<ul>
<li>Use context for inputs by <a href="https://github.com/crazy-max"><code>@​crazy-max</code></a> (<a href="https://redirect.github.com/docker/setup-qemu-action/issues/62">#62</a>)</li>
<li>Use built-in <code>getExecOutput</code> by <a href="https://github.com/crazy-max"><code>@​crazy-max</code></a> (<a href="https://redirect.github.com/docker/setup-qemu-action/issues/61">#61</a>)</li>
<li>Remove workaround for <code>setOutput</code> by <a href="https://github.com/crazy-max"><code>@​crazy-max</code></a> (<a href="https://redirect.github.com/docker/setup-qemu-action/issues/63">#63</a>)</li>
<li>Bump <code>@​actions/core</code> from 1.6.0 to 1.10.0 (<a href="https://redirect.github.com/docker/setup-qemu-action/issues/54">#54</a> <a href="https://redirect.github.com/docker/setup-qemu-action/issues/58">#58</a> <a href="https://redirect.github.com/docker/setup-qemu-action/issues/59">#59</a>)</li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/docker/setup-qemu-action/compare/v2.0.0...v2.1.0">https://github.com/docker/setup-qemu-action/compare/v2.0.0...v2.1.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/docker/setup-qemu-action/commit/68827325e0b33c7199eb31dd4e31fbe9023e06e3"><code>6882732</code></a> Merge pull request <a href="https://redirect.github.com/docker/setup-qemu-action/issues/103">#103</a> from docker/dependabot/npm_and_yarn/actions/core-1.10.1</li>
<li><a href="https://github.com/docker/setup-qemu-action/commit/183f4af5043c24c5dcd58e6e9868b7df0a201a17"><code>183f4af</code></a> chore: update generated content</li>
<li><a href="https://github.com/docker/setup-qemu-action/commit/f17493529ee8f8b9fd58ed31aa1e9816c8345eb8"><code>f174935</code></a> build(deps): bump <code>@​actions/core</code> from 1.10.0 to 1.10.1</li>
<li><a href="https://github.com/docker/setup-qemu-action/commit/2e423eb50075d10a6af463e39e267d4057cc07bb"><code>2e423eb</code></a> Merge pull request <a href="https://redirect.github.com/docker/setup-qemu-action/issues/89">#89</a> from docker/dependabot/npm_and_yarn/semver-6.3.1</li>
<li><a href="https://github.com/docker/setup-qemu-action/commit/ecc406afa7d79acce0ed8a04bb5eb086136fd000"><code>ecc406a</code></a> Bump semver from 6.3.0 to 6.3.1</li>
<li><a href="https://github.com/docker/setup-qemu-action/commit/12dec5e201ab2f6dbea47c28f4a138590189069a"><code>12dec5e</code></a> Merge pull request <a href="https://redirect.github.com/docker/setup-qemu-action/issues/102">#102</a> from crazy-max/update-node20</li>
<li><a href="https://github.com/docker/setup-qemu-action/commit/c29b31213096fed2f8b8b36e66385d52653bf332"><code>c29b312</code></a> chore: node 20 as default runtime</li>
<li><a href="https://github.com/docker/setup-qemu-action/commit/34ae628c8f4fb0d5c8921b9a0ffa9dcf66120c07"><code>34ae628</code></a> chore: update generated content</li>
<li><a href="https://github.com/docker/setup-qemu-action/commit/1f3d2e1ac09548bbfb7d44b1809ac66e3d551ea4"><code>1f3d2e1</code></a> chore: fix author in package.json</li>
<li><a href="https://github.com/docker/setup-qemu-action/commit/277dbe8c9c1990b42dc38cb1f35a593889a31a62"><code>277dbe8</code></a> vendor: bump <code>@​docker/actions-toolkit</code> from 0.3.0 to 0.12.0</li>
<li>Additional commits viewable in <a href="https://github.com/docker/setup-qemu-action/compare/v2...v3">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/setup-qemu-action&package-manager=github_actions&previous-version=2&new-version=3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

coveralls: ## Pull Request Test Coverage Report for [Build 7964351453](https://coveralls.io/builds/65786823)


### Details

* **0** of **0**   changed or added relevant lines in **0** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at **55.796%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/65786823/badge)](https://coveralls.io/builds/65786823) |
| :-- | --: |
| Change from base [Build 7946284521](https://coveralls.io/builds/65762224): |  0.0% |
| Covered Lines: | 2686 |
| Relevant Lines: | 4814 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
index 9ba50d5..1f85390 100644
--- a/.github/workflows/docker-build.yml
+++ b/.github/workflows/docker-build.yml
@@ -24,7 +24,7 @@ jobs:
 
       - name: Docker meta
         id: meta
-        uses: docker/metadata-action@v4
+        uses: docker/metadata-action@v5
         with:
           images: |
             inbucket/inbucket
@@ -39,7 +39,7 @@ jobs:
             latest=auto
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v2
+        uses: docker/setup-qemu-action@v3
         with:
           platforms: all
 
@@ -62,7 +62,7 @@ jobs:
           password: ${{ secrets.GITHUB_TOKEN }}
 
       - name: Build and push
-        uses: docker/build-push-action@v4
+        uses: docker/build-push-action@v5
         with:
           context: .
           platforms: linux/amd64,linux/arm64, linux/arm/v7
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c58a876..3d2d5a1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -37,14 +37,14 @@ jobs:
         working-directory: ./ui
 
       - name: Test build release
-        uses: goreleaser/goreleaser-action@v4
+        uses: goreleaser/goreleaser-action@v5
         if: "!startsWith(github.ref, 'refs/tags/v')"
         with:
           version: latest
           args: release --snapshot
 
       - name: Build and publish release
-        uses: goreleaser/goreleaser-action@v4
+        uses: goreleaser/goreleaser-action@v5
         if: "startsWith(github.ref, 'refs/tags/v')"
         with:
           version: latest
diff --git a/pkg/server/smtp/handler.go b/pkg/server/smtp/handler.go
index 33c3866..1281b5a 100644
--- a/pkg/server/smtp/handler.go
+++ b/pkg/server/smtp/handler.go
@@ -139,20 +139,23 @@ func (s *Session) String() string {
 	return fmt.Sprintf("Session{id: %v, state: %v}", s.id, s.state)
 }
 
-/* Session flow:
- *  1. Send initial greeting
- *  2. Receive cmd
- *  3. If good cmd, respond, optionally change state
- *  4. If bad cmd, respond error
- *  5. Goto 2
- */
+// Session flow:
+//  1. Send initial greeting
+//  2. Receive cmd
+//  3. If good cmd, respond, optionally change state
+//  4. If bad cmd, respond error
+//  5. Goto 2
 func (s *Server) startSession(id int, conn net.Conn, logger zerolog.Logger) {
 	logger = logger.Hook(logHook{}).With().
 		Str("module", "smtp").
 		Str("remote", conn.RemoteAddr().String()).
 		Int("session", id).Logger()
 	logger.Info().Msg("Starting SMTP session")
+
+	// Update WaitGroup and counters.
+	s.wg.Add(1)
 	expConnectsCurrent.Add(1)
+	expConnectsTotal.Add(1)
 	defer func() {
 		if err := conn.Close(); err != nil {
 			logger.Warn().Err(err).Msg("Closing connection")
diff --git a/pkg/server/smtp/listener.go b/pkg/server/smtp/listener.go
index b67648a..b8d7e10 100644
--- a/pkg/server/smtp/listener.go
+++ b/pkg/server/smtp/listener.go
@@ -176,8 +176,6 @@ func (s *Server) serve(ctx context.Context) {
 			}
 		} else {
 			tempDelay = 0
-			expConnectsTotal.Add(1)
-			s.wg.Add(1)
 			go s.startSession(sessionID, conn, log.Logger)
 		}
 	}


Test patch:
diff --git a/pkg/rest/testutils_test.go b/pkg/rest/testutils_test.go
index acd8913..c534193 100644
--- a/pkg/rest/testutils_test.go
+++ b/pkg/rest/testutils_test.go
@@ -2,12 +2,14 @@ package rest
 
 import (
 	"bytes"
+	"context"
 	"log"
 	"net/http"
 	"net/http/httptest"
 	"strconv"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/inbucket/inbucket/v3/pkg/config"
 	"github.com/inbucket/inbucket/v3/pkg/message"
@@ -16,24 +18,36 @@ import (
 )
 
 func testRestGet(url string) (*httptest.ResponseRecorder, error) {
-	req, err := http.NewRequest("GET", url, nil)
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
 	req.Header.Add("Accept", "application/json")
 	if err != nil {
 		return nil, err
 	}
+
+	// Pass request to handlers directly.
 	w := httptest.NewRecorder()
 	web.Router.ServeHTTP(w, req)
+
 	return w, nil
 }
 
 func testRestPatch(url string, body string) (*httptest.ResponseRecorder, error) {
-	req, err := http.NewRequest("PATCH", url, strings.NewReader(body))
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+
+	req, err := http.NewRequestWithContext(ctx, http.MethodPatch, url, strings.NewReader(body))
 	req.Header.Add("Accept", "application/json")
 	if err != nil {
 		return nil, err
 	}
+
+	// Pass request to handlers directly.
 	w := httptest.NewRecorder()
 	web.Router.ServeHTTP(w, req)
+
 	return w, nil
 }
 
diff --git a/pkg/server/smtp/handler_test.go b/pkg/server/smtp/handler_test.go
index f86d113..ac3eb78 100644
--- a/pkg/server/smtp/handler_test.go
+++ b/pkg/server/smtp/handler_test.go
@@ -45,7 +45,6 @@ func TestGreetStateValidCommands(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.send, func(t *testing.T) {
-			defer server.Drain() // Required to prevent test logging data race.
 			script := []scriptStep{
 				tc,
 				{"QUIT", 221}}
@@ -58,7 +57,6 @@ func TestGreetStateValidCommands(t *testing.T) {
 func TestGreetState(t *testing.T) {
 	ds := test.NewStore()
 	server := setupSMTPServer(ds, extension.NewHost())
-	defer server.Drain() // Required to prevent test logging data race.
 
 	tests := []scriptStep{
 		{"HELO", 501},
@@ -71,7 +69,6 @@ func TestGreetState(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.send, func(t *testing.T) {
-			defer server.Drain() // Required to prevent test logging data race.
 			script := []scriptStep{
 				tc,
 				{"QUIT", 221}}
@@ -83,7 +80,6 @@ func TestGreetState(t *testing.T) {
 func TestEmptyEnvelope(t *testing.T) {
 	ds := test.NewStore()
 	server := setupSMTPServer(ds, extension.NewHost())
-	defer server.Drain()
 
 	// Test out some empty envelope without blanks
 	script := []scriptStep{
@@ -104,7 +100,6 @@ func TestEmptyEnvelope(t *testing.T) {
 func TestAuth(t *testing.T) {
 	ds := test.NewStore()
 	server := setupSMTPServer(ds, extension.NewHost())
-	defer server.Drain()
 
 	// PLAIN AUTH
 	script := []scriptStep{
@@ -137,7 +132,6 @@ func TestAuth(t *testing.T) {
 func TestTLS(t *testing.T) {
 	ds := test.NewStore()
 	server := setupSMTPServer(ds, extension.NewHost())
-	defer server.Drain()
 
 	// Test Start TLS parsing.
 	script := []scriptStep{
@@ -172,7 +166,6 @@ func TestReadyStateValidCommands(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.send, func(t *testing.T) {
-			defer server.Drain()
 			script := []scriptStep{
 				{"HELO localhost", 250},
 				tc,
@@ -196,7 +189,6 @@ func TestReadyStateRejectedDomains(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.send, func(t *testing.T) {
-			defer server.Drain()
 			script := []scriptStep{
 				{"HELO localhost", 250},
 				tc,
@@ -226,7 +218,6 @@ func TestReadyStateInvalidCommands(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.send, func(t *testing.T) {
-			defer server.Drain()
 			script := []scriptStep{
 				{"HELO localhost", 250},
 				tc,
@@ -240,7 +231,6 @@ func TestReadyStateInvalidCommands(t *testing.T) {
 func TestMailState(t *testing.T) {
 	mds := test.NewStore()
 	server := setupSMTPServer(mds, extension.NewHost())
-	defer server.Drain()
 
 	// Test out some mangled READY commands
 	script := []scriptStep{
@@ -333,7 +323,6 @@ func TestMailState(t *testing.T) {
 func TestDataState(t *testing.T) {
 	mds := test.NewStore()
 	server := setupSMTPServer(mds, extension.NewHost())
-	defer server.Drain()
 
 	var script []scriptStep
 	pipe := setupSMTPSession(t, server)
@@ -395,54 +384,11 @@ Hi!
 	_, _, _ = c.ReadCodeLine(221)
 }
 
-// playSession creates a new session, reads the greeting and then plays the script
-func playSession(t *testing.T, server *Server, script []scriptStep) {
-	t.Helper()
-	pipe := setupSMTPSession(t, server)
-	c := textproto.NewConn(pipe)
-
-	if code, _, err := c.ReadCodeLine(220); err != nil {
-		t.Errorf("expected a 220 greeting, got %v", code)
-	}
-
-	playScriptAgainst(t, c, script)
-
-	// Not all tests leave the session in a clean state, so the following two calls can fail
-	_, _ = c.Cmd("QUIT")
-	_, _, _ = c.ReadCodeLine(221)
-}
-
-// playScriptAgainst an existing connection, does not handle server greeting
-func playScriptAgainst(t *testing.T, c *textproto.Conn, script []scriptStep) {
-	t.Helper()
-
-	for i, step := range script {
-		id, err := c.Cmd(step.send)
-		if err != nil {
-			t.Fatalf("Step %d, failed to send %q: %v", i, step.send, err)
-		}
-
-		c.StartResponse(id)
-		code, msg, err := c.ReadResponse(step.expect)
-		if err != nil {
-			err = fmt.Errorf("Step %d, sent %q, expected %v, got %v: %q",
-				i, step.send, step.expect, code, msg)
-		}
-		c.EndResponse(id)
-
-		if err != nil {
-			// Fail after c.EndResponse so we don't hang the connection
-			t.Fatal(err)
-		}
-	}
-}
-
 // Tests "MAIL FROM" emits BeforeMailAccepted event.
 func TestBeforeMailAcceptedEventEmitted(t *testing.T) {
 	ds := test.NewStore()
 	extHost := extension.NewHost()
 	server := setupSMTPServer(ds, extHost)
-	defer server.Drain()
 
 	var got *event.AddressParts
 	extHost.Events.BeforeMailAccepted.AddListener(
@@ -469,7 +415,6 @@ func TestBeforeMailAcceptedEventResponse(t *testing.T) {
 	ds := test.NewStore()
 	extHost := extension.NewHost()
 	server := setupSMTPServer(ds, extHost)
-	defer server.Drain()
 
 	var shouldReturn *bool
 	var gotEvent *event.AddressParts
@@ -519,6 +464,48 @@ func TestBeforeMailAcceptedEventResponse(t *testing.T) {
 	}
 }
 
+// playSession creates a new session, reads the greeting and then plays the script
+func playSession(t *testing.T, server *Server, script []scriptStep) {
+	t.Helper()
+	pipe := setupSMTPSession(t, server)
+	c := textproto.NewConn(pipe)
+
+	if code, _, err := c.ReadCodeLine(220); err != nil {
+		t.Errorf("expected a 220 greeting, got %v", code)
+	}
+
+	playScriptAgainst(t, c, script)
+
+	// Not all tests leave the session in a clean state, so the following two calls can fail
+	_, _ = c.Cmd("QUIT")
+	_, _, _ = c.ReadCodeLine(221)
+}
+
+// playScriptAgainst an existing connection, does not handle server greeting
+func playScriptAgainst(t *testing.T, c *textproto.Conn, script []scriptStep) {
+	t.Helper()
+
+	for i, step := range script {
+		id, err := c.Cmd(step.send)
+		if err != nil {
+			t.Fatalf("Step %d, failed to send %q: %v", i, step.send, err)
+		}
+
+		c.StartResponse(id)
+		code, msg, err := c.ReadResponse(step.expect)
+		if err != nil {
+			err = fmt.Errorf("Step %d, sent %q, expected %v, got %v: %q",
+				i, step.send, step.expect, code, msg)
+		}
+		c.EndResponse(id)
+
+		if err != nil {
+			// Fail after c.EndResponse so we don't hang the connection
+			t.Fatal(err)
+		}
+	}
+}
+
 // net.Pipe does not implement deadlines
 type mockConn struct {
 	net.Conn
@@ -528,6 +515,7 @@ func (m *mockConn) SetDeadline(t time.Time) error      { return nil }
 func (m *mockConn) SetReadDeadline(t time.Time) error  { return nil }
 func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil }
 
+// Creates an unstarted smtp.Server.
 func setupSMTPServer(ds storage.Store, extHost *extension.Host) *Server {
 	cfg := &config.Root{
 		MailboxNaming: config.FullNaming,
@@ -543,7 +531,7 @@ func setupSMTPServer(ds storage.Store, extHost *extension.Host) *Server {
 		},
 	}
 
-	// Create a server, don't start it.
+	// Create a server, but don't start it.
 	addrPolicy := &policy.Addressing{Config: cfg}
 	manager := &message.StoreManager{Store: ds, ExtHost: extHost}
 
@@ -556,9 +544,15 @@ func setupSMTPSession(t *testing.T, server *Server) net.Conn {
 	t.Helper()
 	logger := zerolog.New(zerolog.NewTestWriter(t))
 	serverConn, clientConn := net.Pipe()
+	t.Cleanup(func() {
+		_ = clientConn.Close()
+
+		// Drain is required to prevent a test-logging data race. If a (failing) test run is
+		// hanging, this may be the culprit.
+		server.Drain()
+	})
 
 	// Start the session.
-	server.wg.Add(1)
 	sessionNum++
 	go server.startSession(sessionNum, &mockConn{serverConn}, logger)