From b8048cc556aa96e4321870af23f7397288b8f502 Mon Sep 17 00:00:00 2001 From: Vladislav Trotsenko Date: Tue, 20 Feb 2024 16:13:31 +0100 Subject: [PATCH 1/7] Technical/Add commitspell linter (#170) * Added commitspell linter, configuration * Updated lefthook config --- .circleci/linter_configs/.commitspell.yml | 44 +++++++++++++++++++++++ .circleci/linter_configs/.lefthook.yml | 2 ++ .circleci/scripts/commitspell.sh | 22 ++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 .circleci/linter_configs/.commitspell.yml create mode 100755 .circleci/scripts/commitspell.sh diff --git a/.circleci/linter_configs/.commitspell.yml b/.circleci/linter_configs/.commitspell.yml new file mode 100644 index 0000000..6c9b7ce --- /dev/null +++ b/.circleci/linter_configs/.commitspell.yml @@ -0,0 +1,44 @@ +--- + +enableGlobDot: true + +patterns: + - name: GithubUser + pattern: /\[@.+\]/gmx + +languageSettings: + - languageId: markdown + ignoreRegExpList: + - Email + - GithubUser + +words: + - autobuilds + - bestwebua + - codecov + - codesmells + - commitspell + - consts + - crtypto + - funcs + - unexported + - golangci + - gomod + - gotestsum + - goreleaser + - lefthook + - ldflags + - markdownlint + - mocktools + - punycode + - rubocop + - shellcheck + - shortcuting + - smtpmock + - sigquit + - struct + - structs + - yamlint + - rset + - rcptto + - helo diff --git a/.circleci/linter_configs/.lefthook.yml b/.circleci/linter_configs/.lefthook.yml index 6f978c3..0f9a892 100644 --- a/.circleci/linter_configs/.lefthook.yml +++ b/.circleci/linter_configs/.lefthook.yml @@ -6,6 +6,8 @@ skip_output: linters: commands: + commitspell: + run: .circleci/scripts/commitspell.sh -c '.circleci/linter_configs/.commitspell.yml' cspell: run: cspell-cli lint -c '.circleci/linter_configs/.cspell.yml' '**/*.{txt,md}' golangci: diff --git a/.circleci/scripts/commitspell.sh b/.circleci/scripts/commitspell.sh new file mode 100755 index 0000000..d284cd0 --- /dev/null +++ b/.circleci/scripts/commitspell.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -e + +configuration=$(if [ "$2" = "" ]; then echo "$2"; else echo " $1 $2"; fi) +latest_commit=$(git rev-parse HEAD) + +spellcheck_info() { + echo "Checking the spelling of the latest commit ($latest_commit) message..." +} + +compose_cspell_command() { + echo "cspell-cli lint stdin$configuration" +} + +cspell="$(compose_cspell_command)" + +spellcheck_latest_commit() { + git log -1 --pretty=%B | $cspell +} + +spellcheck_info +spellcheck_latest_commit From c54b8d79b83b1f9302eb7a85278e1f0e2ba81265 Mon Sep 17 00:00:00 2001 From: Mitar Date: Thu, 29 Feb 2024 23:22:13 -0800 Subject: [PATCH 2/7] Bugfix/Fix Messages race conditions (#173) See: https://github.com/mocktools/go-smtp-mock/issues/150 --- message.go | 21 +++++++++++++++++++-- message_test.go | 14 ++++++++++++++ server.go | 24 +++++++----------------- server_test.go | 34 ++++++++++++++++------------------ smtpmock_test.go | 25 +++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 37 deletions(-) diff --git a/message.go b/message.go index 68a7a01..52deca0 100644 --- a/message.go +++ b/message.go @@ -145,16 +145,33 @@ var zeroMessage = &Message{} // Concurrent type that can be safely shared between goroutines type messages struct { - sync.Mutex + sync.RWMutex items []*Message } // messages methods -// Addes new message pointer into concurrent messages slice +// Adds new message pointer into concurrent messages slice func (messages *messages) append(item *Message) { messages.Lock() defer messages.Unlock() messages.items = append(messages.items, item) } + +// Returns a copy of all messages +func (messages *messages) copy() []Message { + messages.RLock() + defer messages.RUnlock() + + return messages.copyInternal() +} + +// Copy without a lock +func (messages *messages) copyInternal() []Message { + copiedMessages := []Message{} + for index := range messages.items { + copiedMessages = append(copiedMessages, *messages.items[index]) + } + return copiedMessages +} diff --git a/message_test.go b/message_test.go index 27c4e51..b6fa2f9 100644 --- a/message_test.go +++ b/message_test.go @@ -239,6 +239,20 @@ func TestMessagesAppend(t *testing.T) { message, messages := new(Message), new(messages) messages.append(message) + messages.RLock() assert.Same(t, message, messages.items[0]) + messages.RUnlock() + }) +} + +func TestMessagesCopy(t *testing.T) { + t.Run("copies messages", func(t *testing.T) { + message, messages := new(Message), new(messages) + message.heloRequest = "foobar" + messages.append(message) + + copyMessages := messages.copy() + assert.Len(t, copyMessages, 1) + assert.Equal(t, *message, copyMessages[0]) }) } diff --git a/server.go b/server.go index 46fca52..8f3baac 100644 --- a/server.go +++ b/server.go @@ -121,14 +121,7 @@ func (server *Server) Stop() (err error) { // Public interface to get access to server messages. // Returns slice with copy of messages func (server *Server) Messages() []Message { - server.Lock() - defer server.Unlock() - copiedMessages, messages := []Message{}, server.messages.items - for index := range messages { - copiedMessages = append(copiedMessages, *messages[index]) - } - - return copiedMessages + return server.messages.copy() } // Thread-safe getter of server port. @@ -175,19 +168,13 @@ func (server *Server) stop() { server.started = false } -// Creates and assigns new message to server.messages -func (server *Server) newMessage() *Message { - newMessage := new(Message) - server.messages.append(newMessage) - return newMessage -} - // Creates and assigns new message with helo context from other message to server.messages func (server *Server) newMessageWithHeloContext(otherMessage *Message) *Message { - newMessage := server.newMessage() + newMessage := new(Message) newMessage.heloRequest = otherMessage.heloRequest newMessage.heloResponse = otherMessage.heloResponse newMessage.helo = otherMessage.helo + server.messages.append(otherMessage) return newMessage } @@ -221,7 +208,10 @@ func (server *Server) isAbleToEndSession(message *Message, session sessionInterf //nolint:gocyclo // SMTP client-server session handler func (server *Server) handleSession(session sessionInterface) { defer session.finish() - message, configuration := server.newMessage(), server.configuration + message, configuration := new(Message), server.configuration + defer func() { + server.messages.append(message) + }() session.writeResponse(configuration.msgGreeting, defaultSessionResponseDelay) for { diff --git a/server_test.go b/server_test.go index 4b6a9ab..0efa930 100644 --- a/server_test.go +++ b/server_test.go @@ -33,7 +33,7 @@ func TestServerStart(t *testing.T) { assert.NoError(t, server.Start()) _ = runSuccessfulSMTPSession(configuration.hostAddress, server.PortNumber(), false) - assert.NotEmpty(t, server.messages) + assert.NotNil(t, server.messages) assert.NotNil(t, server.quit) assert.NotNil(t, server.quitTimeout) assert.True(t, server.isStarted()) @@ -49,7 +49,7 @@ func TestServerStart(t *testing.T) { assert.NoError(t, server.Start()) _ = runSuccessfulSMTPSession(configuration.hostAddress, portNumber, false) - assert.NotEmpty(t, server.messages) + assert.NotNil(t, server.messages) assert.NotNil(t, server.quit) assert.NotNil(t, server.quitTimeout) assert.True(t, server.isStarted()) @@ -139,7 +139,8 @@ func TestServerMessages(t *testing.T) { t.Run("when there are messages on the server", func(t *testing.T) { server := newServer(configuration) - server.newMessage() + message := new(Message) + server.messages.append(message) assert.NotEmpty(t, server.Messages()) }) @@ -147,15 +148,20 @@ func TestServerMessages(t *testing.T) { t.Run("message data are identical", func(t *testing.T) { server := newServer(configuration) + server.messages.RLock() assert.Empty(t, server.messages.items) assert.Empty(t, server.Messages()) assert.NotSame(t, server.messages.items, server.Messages()) + server.messages.RUnlock() - message := server.newMessage() + message := new(Message) + server.messages.append(message) + server.messages.RLock() assert.Equal(t, []*Message{message}, server.messages.items) assert.Equal(t, []Message{*message}, server.Messages()) assert.NotSame(t, server.messages.items, server.Messages()) + server.messages.RUnlock() }) } @@ -213,29 +219,21 @@ func TestServerStopFlag(t *testing.T) { }) } -func TestServerNewMessage(t *testing.T) { - t.Run("pushes new message into server.messages, returns this message", func(t *testing.T) { - server := &Server{messages: new(messages)} - message, messages := server.newMessage(), server.messages.items - - assert.NotEmpty(t, messages) - assert.Equal(t, message, messages[0]) - }) -} - func TestServerNewMessageWithHeloContext(t *testing.T) { t.Run("pushes new message into server.messages with helo context from other message, returns this message", func(t *testing.T) { server := &Server{messages: new(messages)} - message, heloRequest, heloResponse, helo := server.newMessage(), "heloRequest", "heloResponse", true + message, heloRequest, heloResponse, helo := new(Message), "heloRequest", "heloResponse", true message.heloRequest, message.heloResponse, message.helo = heloRequest, heloResponse, helo newMessage := server.newMessageWithHeloContext(message) - messages := server.messages.items + server.messages.RLock() + messages := server.messages.items assert.Equal(t, heloRequest, newMessage.heloRequest) assert.Equal(t, heloResponse, newMessage.heloResponse) assert.Equal(t, helo, newMessage.helo) - assert.Equal(t, newMessage, messages[1]) - assert.Equal(t, 2, len(messages)) + assert.Equal(t, newMessage, messages[0]) + assert.Equal(t, 1, len(messages)) + server.messages.RUnlock() }) } diff --git a/smtpmock_test.go b/smtpmock_test.go index ba64cc6..7234354 100644 --- a/smtpmock_test.go +++ b/smtpmock_test.go @@ -3,6 +3,7 @@ package smtpmock import ( "fmt" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -205,3 +206,27 @@ func TestNew(t *testing.T) { assert.True(t, secondMessage.quitSent) }) } + +func TestRace(t *testing.T) { + hostName := "localhost" + server := New(ConfigurationAttr{ + HostAddress: hostName, + }) + + if err := server.Start(); err != nil { + t.Log(err) + t.FailNow() + } + + go func() { + _ = runSuccessfulSMTPSession(hostName, server.PortNumber(), true) + }() + + time.Sleep(1 * time.Second) + assert.Len(t, server.Messages(), 1) + + if err := server.Stop(); err != nil { + t.Log(err) + t.FailNow() + } +} From 88a83d2e821d057747bed5572a20a426b3560e40 Mon Sep 17 00:00:00 2001 From: Mitar Date: Fri, 1 Mar 2024 00:32:56 -0800 Subject: [PATCH 3/7] Bufix/Remove duplicate code (#175) --- handler_rset.go | 2 +- message.go | 7 ------- message_test.go | 10 +++++----- server.go | 2 +- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/handler_rset.go b/handler_rset.go index 23cb700..a107d9b 100644 --- a/handler_rset.go +++ b/handler_rset.go @@ -31,7 +31,7 @@ func (handler *handlerRset) run(request string) { func (handler *handlerRset) clearMessage() { messageWithData, configuration := handler.message, handler.configuration - if !(configuration.multipleMessageReceiving && messageWithData.isConsistent()) { + if !(configuration.multipleMessageReceiving && messageWithData.IsConsistent()) { clearedMessage := &Message{ heloRequest: messageWithData.heloRequest, heloResponse: messageWithData.heloResponse, diff --git a/message.go b/message.go index 52deca0..fa955a0 100644 --- a/message.go +++ b/message.go @@ -121,13 +121,6 @@ func (message Message) IsConsistent() bool { return message.mailfrom && message.rcptto && message.data && message.msg } -// Message pointer consistency status predicate. Returns true for case -// when message struct is consistent. It means that MAILFROM, RCPTTO, DATA -// commands and message context were successful. Otherwise returns false -func (message *Message) isConsistent() bool { - return message.mailfrom && message.rcptto && message.data && message.msg -} - // Message RCPTTO successful response predicate. Returns true when at least one // successful RCPTTO response exists. Otherwise returns false func (message *Message) isIncludesSuccessfulRcpttoResponse(targetSuccessfulResponse string) bool { diff --git a/message_test.go b/message_test.go index b6fa2f9..51fcb3d 100644 --- a/message_test.go +++ b/message_test.go @@ -193,30 +193,30 @@ func TestMessagePointerIsConsistent(t *testing.T) { t.Run("when consistent", func(t *testing.T) { message := &Message{mailfrom: true, rcptto: true, data: true, msg: true} - assert.True(t, message.isConsistent()) + assert.True(t, message.IsConsistent()) }) t.Run("when not consistent MAILFROM", func(t *testing.T) { - assert.False(t, new(Message).isConsistent()) + assert.False(t, new(Message).IsConsistent()) }) t.Run("when not consistent RCPTTO", func(t *testing.T) { message := &Message{mailfrom: true} - assert.False(t, message.isConsistent()) + assert.False(t, message.IsConsistent()) }) t.Run("when not consistent DATA", func(t *testing.T) { message := &Message{mailfrom: true, rcptto: true} - assert.False(t, message.isConsistent()) + assert.False(t, message.IsConsistent()) }) t.Run("when not consistent MSG", func(t *testing.T) { message := &Message{mailfrom: true, rcptto: true, data: true} - assert.False(t, message.isConsistent()) + assert.False(t, message.IsConsistent()) }) } diff --git a/server.go b/server.go index 8f3baac..13ed0e3 100644 --- a/server.go +++ b/server.go @@ -234,7 +234,7 @@ func (server *Server) handleSession(session sessionInterface) { case "HELO", "EHLO": newHandlerHelo(session, message, configuration).run(request) case "MAIL": - if configuration.multipleMessageReceiving && message.rset && message.isConsistent() { + if configuration.multipleMessageReceiving && message.rset && message.IsConsistent() { message = server.newMessageWithHeloContext(message) } From e6ad9908ada485b96e18fafcdb7232dcdfc125c2 Mon Sep 17 00:00:00 2001 From: Vladislav Trotsenko Date: Fri, 1 Mar 2024 09:42:35 +0100 Subject: [PATCH 4/7] Technical/Refactor codebase (#176) --- message.go | 5 ++--- message_test.go | 2 +- smtpmock_test.go | 35 +++++++++++++++++------------------ 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/message.go b/message.go index fa955a0..5206fc8 100644 --- a/message.go +++ b/message.go @@ -148,7 +148,6 @@ type messages struct { func (messages *messages) append(item *Message) { messages.Lock() defer messages.Unlock() - messages.items = append(messages.items, item) } @@ -156,15 +155,15 @@ func (messages *messages) append(item *Message) { func (messages *messages) copy() []Message { messages.RLock() defer messages.RUnlock() - return messages.copyInternal() } -// Copy without a lock +// Copy messages without a lock func (messages *messages) copyInternal() []Message { copiedMessages := []Message{} for index := range messages.items { copiedMessages = append(copiedMessages, *messages.items[index]) } + return copiedMessages } diff --git a/message_test.go b/message_test.go index 51fcb3d..09e12b2 100644 --- a/message_test.go +++ b/message_test.go @@ -250,8 +250,8 @@ func TestMessagesCopy(t *testing.T) { message, messages := new(Message), new(messages) message.heloRequest = "foobar" messages.append(message) - copyMessages := messages.copy() + assert.Len(t, copyMessages, 1) assert.Equal(t, *message, copyMessages[0]) }) diff --git a/smtpmock_test.go b/smtpmock_test.go index 7234354..95d949c 100644 --- a/smtpmock_test.go +++ b/smtpmock_test.go @@ -207,26 +207,25 @@ func TestNew(t *testing.T) { }) } -func TestRace(t *testing.T) { - hostName := "localhost" - server := New(ConfigurationAttr{ - HostAddress: hostName, - }) +func TestServerMessagesRaceCondition(t *testing.T) { + t.Run("runs without race condition for server.Messages()", func(t *testing.T) { + server := New(ConfigurationAttr{}) - if err := server.Start(); err != nil { - t.Log(err) - t.FailNow() - } + if err := server.Start(); err != nil { + t.Log(err) + t.FailNow() + } - go func() { - _ = runSuccessfulSMTPSession(hostName, server.PortNumber(), true) - }() + go func() { + _ = runSuccessfulSMTPSession(server.configuration.hostAddress, server.PortNumber(), true) + }() - time.Sleep(1 * time.Second) - assert.Len(t, server.Messages(), 1) + time.Sleep(1 * time.Second) + assert.Len(t, server.Messages(), 1) - if err := server.Stop(); err != nil { - t.Log(err) - t.FailNow() - } + if err := server.Stop(); err != nil { + t.Log(err) + t.FailNow() + } + }) } From be35e5a60f11d2bba3ae39b5785c9379f0fe0088 Mon Sep 17 00:00:00 2001 From: Mitar Date: Fri, 1 Mar 2024 04:10:32 -0800 Subject: [PATCH 5/7] Feature/Add MessagesAndPurge to server (#174) --- message.go | 11 +++++++++++ message_test.go | 11 +++++++++++ server.go | 6 ++++++ server_test.go | 10 ++++++++++ 4 files changed, 38 insertions(+) diff --git a/message.go b/message.go index 5206fc8..4b3cea5 100644 --- a/message.go +++ b/message.go @@ -167,3 +167,14 @@ func (messages *messages) copyInternal() []Message { return copiedMessages } + +// Returns all messages and removes them at the same time +func (messages *messages) purge() []Message { + messages.Lock() + defer messages.Unlock() + + copiedMessages := messages.copyInternal() + messages.items = nil + + return copiedMessages +} diff --git a/message_test.go b/message_test.go index 09e12b2..8b5da6f 100644 --- a/message_test.go +++ b/message_test.go @@ -256,3 +256,14 @@ func TestMessagesCopy(t *testing.T) { assert.Equal(t, *message, copyMessages[0]) }) } + +func TestMessagesPurge(t *testing.T) { + t.Run("purges messages from items slice", func(t *testing.T) { + message, messages := new(Message), new(messages) + messages.append(message) + + assert.Len(t, messages.copy(), 1) + assert.Len(t, messages.purge(), 1) + assert.Len(t, messages.copy(), 0) + }) +} diff --git a/server.go b/server.go index 13ed0e3..cb25823 100644 --- a/server.go +++ b/server.go @@ -124,6 +124,12 @@ func (server *Server) Messages() []Message { return server.messages.copy() } +// Public interface to get access to server messages +// and at the same time removes them +func (server *Server) MessagesAndPurge() []Message { + return server.messages.purge() +} + // Thread-safe getter of server port. // Returns server.portNumber func (server *Server) PortNumber() int { diff --git a/server_test.go b/server_test.go index 0efa930..0f2437e 100644 --- a/server_test.go +++ b/server_test.go @@ -163,6 +163,16 @@ func TestServerMessages(t *testing.T) { assert.NotSame(t, server.messages.items, server.Messages()) server.messages.RUnlock() }) + + t.Run("no messages after purge", func(t *testing.T) { + server := newServer(configuration) + message := new(Message) + server.messages.append(message) + + assert.NotEmpty(t, server.Messages()) + assert.NotEmpty(t, server.MessagesAndPurge()) + assert.Empty(t, server.Messages()) + }) } func TestServerPortNumber(t *testing.T) { From 9391072c631304aafe337a981970991a01a07f0f Mon Sep 17 00:00:00 2001 From: Vladislav Trotsenko Date: Sun, 3 Mar 2024 15:10:29 +0100 Subject: [PATCH 6/7] Technical/Add server.Messages() race condition tests (#177) * Updated test helpers * Updated TestServerMessagesRaceCondition() --- .tool-versions | 2 +- server.go | 3 ++- server_test.go | 4 ++-- smtpmock_test.go | 11 ++++++++--- test_helpers_test.go | 9 +++++---- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.tool-versions b/.tool-versions index 3eaf48e..f526442 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -golang 1.21.5 +golang 1.22.0 diff --git a/server.go b/server.go index cb25823..a8284c8 100644 --- a/server.go +++ b/server.go @@ -125,7 +125,8 @@ func (server *Server) Messages() []Message { } // Public interface to get access to server messages -// and at the same time removes them +// and at the same time removes them. +// Returns slice with copy of messages func (server *Server) MessagesAndPurge() []Message { return server.messages.purge() } diff --git a/server_test.go b/server_test.go index 0f2437e..a763089 100644 --- a/server_test.go +++ b/server_test.go @@ -32,7 +32,7 @@ func TestServerStart(t *testing.T) { server := newServer(configuration) assert.NoError(t, server.Start()) - _ = runSuccessfulSMTPSession(configuration.hostAddress, server.PortNumber(), false) + _ = runSuccessfulSMTPSession(configuration.hostAddress, server.PortNumber(), false, 0) assert.NotNil(t, server.messages) assert.NotNil(t, server.quit) assert.NotNil(t, server.quitTimeout) @@ -48,7 +48,7 @@ func TestServerStart(t *testing.T) { server := newServer(configuration) assert.NoError(t, server.Start()) - _ = runSuccessfulSMTPSession(configuration.hostAddress, portNumber, false) + _ = runSuccessfulSMTPSession(configuration.hostAddress, portNumber, false, 0) assert.NotNil(t, server.messages) assert.NotNil(t, server.quit) assert.NotNil(t, server.quitTimeout) diff --git a/smtpmock_test.go b/smtpmock_test.go index 95d949c..07d4271 100644 --- a/smtpmock_test.go +++ b/smtpmock_test.go @@ -155,7 +155,7 @@ func TestNew(t *testing.T) { assert.NoError(t, server.Start()) assert.True(t, server.isStarted()) - _ = runSuccessfulSMTPSession(configuration.hostAddress, server.PortNumber(), true) + _ = runSuccessfulSMTPSession(configuration.hostAddress, server.PortNumber(), true, 0) _ = server.Stop() assert.Equal(t, 2, len(server.Messages())) @@ -217,10 +217,15 @@ func TestServerMessagesRaceCondition(t *testing.T) { } go func() { - _ = runSuccessfulSMTPSession(server.configuration.hostAddress, server.PortNumber(), true) + _ = runSuccessfulSMTPSession(server.configuration.hostAddress, server.PortNumber(), true, 10) }() - time.Sleep(1 * time.Second) + // ensure that server.MessagesAndPurge() doesn't touch messages from active SMTP-session + time.Sleep(5 * time.Millisecond) + assert.Empty(t, server.MessagesAndPurge()) + + // ensure that messages appears after SMTP-session + time.Sleep(100 * time.Millisecond) assert.Len(t, server.Messages(), 1) if err := server.Stop(); err != nil { diff --git a/test_helpers_test.go b/test_helpers_test.go index 239e395..8b4fe1e 100644 --- a/test_helpers_test.go +++ b/test_helpers_test.go @@ -60,10 +60,11 @@ func messageBody(from, to string) []byte { } // Runs full smtp flow -func runFullFlow(client *smtp.Client) error { +func runFullFlow(client *smtp.Client, delay int) error { var err error var wc io.WriteCloser + time.Sleep(time.Duration(delay) * time.Millisecond) sender, receiver1, receiver2, receiver3 := "user@molo.com", "user1@olo.com", "user2@olo.com", "user3@olo.com" if err = client.Mail(sender); err != nil { @@ -112,8 +113,8 @@ func runFullFlow(client *smtp.Client) error { return nil } -// Runs successful SMTP session with target host -func runSuccessfulSMTPSession(hostAddress string, portNumber int, fullFlow bool) error { +// Runs configurable successful SMTP session with target host +func runSuccessfulSMTPSession(hostAddress string, portNumber int, fullFlow bool, delayMs int) error { connection, _ := net.DialTimeout(networkProtocol, serverWithPortNumber(hostAddress, portNumber), time.Duration(2)*time.Second) client, _ := smtp.NewClient(connection, hostAddress) var err error @@ -123,7 +124,7 @@ func runSuccessfulSMTPSession(hostAddress string, portNumber int, fullFlow bool) } if fullFlow { - if err = runFullFlow(client); err != nil { + if err = runFullFlow(client, delayMs); err != nil { return err } } From 376a658bb6b2c4ae09ba0bf9e167e911c025e5df Mon Sep 17 00:00:00 2001 From: Vladislav Trotsenko Date: Sun, 3 Mar 2024 15:10:55 +0100 Subject: [PATCH 7/7] Technical/Update package version (#178) * Updated project documentation * Updated changelog --- CHANGELOG.md | 16 ++++++++++++++++ README.md | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 038ec54..7d437a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.3.0] - 2024-03-03 + +### Added + +- Added ability to message purge when retrieving messages, `server.MessagesAndPurge()`. Thanks [@mitar](https://github.com/mitar) for PR +- Added `commitspell` linter + +### Fixed + +- Fixed issue with data race condition between newMessage() and Messages(). Thanks [@mitar](https://github.com/mitar) for PR + +### Updated + +- Updated `lefthook` config +- Updated project documentation + ## [2.2.1] - 2024-01-25 ### Added diff --git a/README.md b/README.md index e833502..4920a3c 100644 --- a/README.md +++ b/README.md @@ -314,9 +314,13 @@ func main() { client.Close() // Each result of SMTP session will be saved as message. - // To get access to server messages use Messages() method + // To get access for server messages copies use Messages() method server.Messages() + // To get access for server messages copies and purge it on server after + // use MessagesAndPurge() method + server.MessagesAndPurge() + // To stop the server use Stop() method. Please note, smtpmock uses graceful shutdown. // It means that smtpmock will end all sessions after client responses or by session // timeouts immediately.