aerc: test(message): fixture harness for MessageInfo v2 PROPOSED

Jeff Martin: 1
 test(message): fixture harness for MessageInfo

 3 files changed, 124 insertions(+), 0 deletions(-)
#274074 .build.yml success
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/aerc/patches/11811/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH aerc v2] test(message): fixture harness for MessageInfo Export this patch

This change introduces a fixture-based test runner with the eventual
goal of testing against a corpus of desired RFC5322 messages.

I noticed aerc leaves certain messages in the list in the pending "[..]"
state, and I'm unable to view or modify the message. Mostly these
messages are spam, but some are from what I assume are old and
misconfigured mail clients, and other MUAs handle them fine. This patch
provides a simple tool to leave breadcrumbs, provide a debugging aid,
and easy reproducibility for discovered edge cases.

Amended to better signal the intent of the test interface; these
fixtures should all be handled by aerc in some way (without an error).
If desired, fixtures for unresolved issues can be added and executed

TEST_UNRESOLVED=1 go test ./worker/lib -count 1

The larger goal is to make it easy for message handling edge cases to be
saved, and to facilitate asynchronous resolution. Specifically, a
user can submit a bug report along with a patch that adds a fixture to
the code repository with a message that reproduces the error, and then
later a dev can work on it.

For example, in this patch, there's an unresolved fixture containing a
multipart message with a hexa-encoded message part that aerc doesn't
handle. The right fix for this currently looks to me like it may involve
some upstream work in ~emersion/go-message, and will also require some
additional error handling in aerc. This test allows the edge case to be
merged separately from the fix.

The "basic" fixture is taken from
Hello, I added a fixture from a mailbox I encountered in the wild that
aerc doesn't currently handle. Also worked through some feedback from
~labrat about the test interface. This could be safely merged now, or I
can take a stab at the fix when I have the time, and then combine it
with the test. Thanks!

 worker/lib/parse_test.go                    | 89 +++++++++++++++++++++
 worker/lib/testdata/message/basic           |  7 ++
 worker/lib/testdata/message/unresolved/hexa | 28 +++++++
 3 files changed, 124 insertions(+)
 create mode 100644 worker/lib/parse_test.go
 create mode 100644 worker/lib/testdata/message/basic
 create mode 100644 worker/lib/testdata/message/unresolved/hexa

diff --git a/worker/lib/parse_test.go b/worker/lib/parse_test.go
new file mode 100644
index 0000000..e659280
--- /dev/null
+++ b/worker/lib/parse_test.go
@@ -0,0 +1,89 @@
package lib

import (


func TestWorkerParse(t *testing.T) {
	rootDir := "testdata/message"
	msgFiles, err := ioutil.ReadDir(rootDir)

	for _, fi := range msgFiles {
		if fi.IsDir() {

		p := fi.Name()
		t.Run(p, func(t *testing.T) {
			m := NewMockRawMessageFromPath(filepath.Join(rootDir, p))
			if _, err := MessageInfo(m); err != nil {

	unresolvedDir := filepath.Join(rootDir, "unresolved")
	if _, err := os.Stat(unresolvedDir); err != nil {
		t.Skip("no unresolved message fixtures found", err)

	if os.Getenv("TEST_UNRESOLVED") == "" {
		t.Skip("Skipped unresolved test cases")

	unresolvedFiles, err := ioutil.ReadDir(unresolvedDir)

	for _, fi := range unresolvedFiles {
		if fi.IsDir() {

		p := fi.Name()
		t.Run("unresolved/"+p, func(t *testing.T) {
			m := NewMockRawMessageFromPath(filepath.Join(unresolvedDir, p))
			if _, err := MessageInfo(m); err != nil {

type MockRawMessage struct {
	body []byte

func NewMockRawMessage(body []byte) *MockRawMessage {
	return &MockRawMessage{
		body: body,

func NewMockRawMessageFromPath(p string) *MockRawMessage {
	b, err := ioutil.ReadFile(p)
	return NewMockRawMessage(b)

func (m *MockRawMessage) NewReader() (io.Reader, error) {
	return bytes.NewReader(m.body), nil
func (m *MockRawMessage) ModelFlags() ([]models.Flag, error) { return nil, nil }
func (m *MockRawMessage) Labels() ([]string, error)          { return nil, nil }
func (m *MockRawMessage) UID() uint32                        { return 0 }

func die(err error) {
	if err != nil {
diff --git a/worker/lib/testdata/message/basic b/worker/lib/testdata/message/basic
new file mode 100644
index 0000000..8e06445
--- /dev/null
+++ b/worker/lib/testdata/message/basic
@@ -0,0 +1,7 @@
To: Penelope Pussycat <penelope@example.com>, Fabrette Pussycat <fabrette@example.com>
From: Pepé Le Pew <pepe@example.com>
Subject: Ayons asperges pour le déjeuner


Cela ressemble à un excellent recipie[1] déjeuner.
diff --git a/worker/lib/testdata/message/unresolved/hexa b/worker/lib/testdata/message/unresolved/hexa
new file mode 100644
index 0000000..2967a19
--- /dev/null
+++ b/worker/lib/testdata/message/unresolved/hexa
@@ -0,0 +1,28 @@
Subject: Confirmation Needed gUdVJQBhsd
Content-Type: multipart/mixed; boundary="Nextpart_1Q2YJhd197991794467076Pgfa"
To:  <BORK@example.com>
From: ""REGISTRAR"" <zdglopi-1Q2YJhd-noReply@example.com>

Content-Type: multipart/parallel; boundary="sg54sd54g54sdg54"

Content-Type: multipart/alternative; boundary="54qgf54q546f46qsf46qsf"

Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: Hexa

Content-Type: text/html; charset=utf-8

<CeNteR><a hRef="https://example.com-ap-southeast-example.com.com/example.com#qs=r-acacaeehdiebadgdhgghcaegckhabababaggacihaccajfbacccgaehhbkacb"><b><h2>Congratulations Netflix Customer!</h2></b></a><br>


aerc/patches/.build.yml: SUCCESS in 50s

[test(message): fixture harness for MessageInfo][0] v2 from [Jeff Martin][1]

[0]: https://lists.sr.ht/~sircmpwn/aerc/patches/11811
[1]: mailto:jeffmartin@gmail.com

✓ #269244 SUCCESS aerc/patches/.build.yml https://builds.sr.ht/~sircmpwn/job/269244