diff --git a/cmd/age/testdata/fail_bad_hmac.age b/cmd/age/testdata/fail_bad_hmac.age
deleted file mode 100644
index b524055..0000000
--- a/cmd/age/testdata/fail_bad_hmac.age
+++ /dev/null
@@ -1,5 +0,0 @@
-age-encryption.org/v1
--> X25519 i6JOY3uvMdBuEybYbTp3ECFsOPEY/A3lJY1l0Qv2NC4
-cD7VpfIOchU6ZjAccEjlPCNSOdJvVkxZPSf+7XS1YhY
---- 1111111111111111111111111111111111111111111
-ï¿½-\ï¿½P9ï¿½ï¿½0ï¿½hÅ„ï¿½ï¿½Ttï¿½|:Ù˜ï¿½#&Rï¿½rï¿½ ï¿½ï¿½
diff --git a/cmd/age/testdata/fail_large_filekey_x25519.age b/cmd/age/testdata/fail_large_filekey_x25519.age
deleted file mode 100644
index e724828..0000000
--- a/cmd/age/testdata/fail_large_filekey_x25519.age
+++ /dev/null
@@ -1,6 +0,0 @@
-age-encryption.org/v1
--> X25519 UkSgrxSETNpdkHY8EwiiRivqks2QJLUzsNsVjUTDcmw
-8yB9TqsBo4Ypchw07AtemV5TW4sGwyPDPMIfRg8Ve8rbDXt4tCwnnKcMq2K6aoqx
-
---- vUhLU0U9Dc8YhbKy4SxKuq0iSqqjBWGnHfZG+9+O4v4
-À¹õgðò²€h´W‘øéSIúÛf¯Æ†D�õQ;ÐRhê‰w
\ No newline at end of file
diff --git a/cmd/age/testdata/good_empty_payload.age b/cmd/age/testdata/good_empty_payload.age
deleted file mode 100644
index 93e5611..0000000
--- a/cmd/age/testdata/good_empty_payload.age
+++ /dev/null
@@ -1,5 +0,0 @@
-age-encryption.org/v1
--> X25519 JRosIz2avWchP2qSL6wF6U7uzD6kDuJXDbZvN1MOGmo
-KpIQxpkbBDHqp+JsHLiTy2d5RYRwp2qzvUrAe0aDOnk
---- orVjbqbzm8U3S9njAs53o4PFi1wK39fIQQ4gRj3i7IU
-„Ïgñ¾�Ô0‡‘µÆN¢'jûöao�¹&æT
\ No newline at end of file
diff --git a/cmd/age/testdata/good_empty_recipient_body.age b/cmd/age/testdata/good_empty_recipient_body.age
deleted file mode 100644
index d101cf6..0000000
--- a/cmd/age/testdata/good_empty_recipient_body.age
+++ /dev/null
@@ -1,7 +0,0 @@
-age-encryption.org/v1
--> X25519 alRneDshIh43nwyD5+fhuTD5TReSn88f2us4hzZPyzU
-pGduNK5MUhnuzMxW0qbZnC2k7mRzz69bbJpKQrRc7uc
--> A7)h-grease !,_
-
---- 5bA0uXjBxI6wuI5SseCRgD5/G8LkSVISRe/hnrQMb9s
-¦Ã 1‘·¬Žä6_RÙäÚ…›…U<ï1ús®ÿ?`Ö+–’$§HÖW‚v?w8ZW
\ No newline at end of file
diff --git a/internal/testkit/testkit.go b/internal/testkit/testkit.go
index ab18141..8705162 100644
--- a/internal/testkit/testkit.go
+++ b/internal/testkit/testkit.go
@@ -6,7 +6,6 @@ package testkit
 
 import (
 	"bytes"
-	"crypto/cipher"
 	"crypto/hmac"
 	"crypto/sha256"
 	"encoding/base64"
@@ -31,8 +30,9 @@ var TestX25519Recipient, _ = curve25519.X25519(TestX25519Identity, curve25519.Ba
 
 type TestFile struct {
 	Buf  bytes.Buffer
-	rand io.Reader
+	Rand func(n int) []byte
 
+	fileKey    []byte
 	streamKey  []byte
 	nonce      [12]byte
 	payload    bytes.Buffer
@@ -41,19 +41,19 @@ type TestFile struct {
 	identities []string
 }
 
-type zeroReader struct{}
-
-func (zeroReader) Read(p []byte) (int, error) {
-	for n := range p {
-		p[n] = 0
-	}
-	return len(p), nil
-}
-
 func NewTestFile() *TestFile {
 	c, _ := chacha20.NewUnauthenticatedCipher(
 		[]byte("TEST RANDOMNESS TEST RANDOMNESS!"), make([]byte, chacha20.NonceSize))
-	return &TestFile{rand: cipher.StreamReader{c, zeroReader{}}, expect: "success"}
+	rand := func(n int) []byte {
+		out := make([]byte, n)
+		c.XORKeyStream(out, out)
+		return out
+	}
+	return &TestFile{Rand: rand, expect: "success", fileKey: TestFileKey}
+}
+
+func (f *TestFile) FileKey(key []byte) {
+	f.fileKey = key
 }
 
 func (f *TestFile) TextLine(s string) {
@@ -96,18 +96,25 @@ func (f *TestFile) AEADBody(key, body []byte) {
 }
 
 func (f *TestFile) X25519(identity []byte) {
+	f.X25519RecordIdentity(identity)
+	f.X25519NoRecordIdentity(identity)
+}
+
+func (f *TestFile) X25519RecordIdentity(identity []byte) {
 	id, _ := bech32.Encode("AGE-SECRET-KEY-", identity)
 	f.identities = append(f.identities, id)
+}
+
+func (f *TestFile) X25519NoRecordIdentity(identity []byte) {
 	recipient, _ := curve25519.X25519(identity, curve25519.Basepoint)
-	ephemeral := make([]byte, 32)
-	f.rand.Read(ephemeral)
+	ephemeral := f.Rand(32)
 	share, _ := curve25519.X25519(ephemeral, curve25519.Basepoint)
 	f.ArgsLine("X25519", b64(share))
 	secret, _ := curve25519.X25519(ephemeral, recipient)
 	key := make([]byte, 32)
 	hkdf.New(sha256.New, secret, append(share, recipient...),
 		[]byte("age-encryption.org/v1/X25519")).Read(key)
-	f.AEADBody(key, TestFileKey)
+	f.AEADBody(key, f.fileKey)
 }
 
 func (f *TestFile) HMACLine(h []byte) {
@@ -116,7 +123,7 @@ func (f *TestFile) HMACLine(h []byte) {
 
 func (f *TestFile) HMAC() {
 	key := make([]byte, 32)
-	hkdf.New(sha256.New, TestFileKey, nil, []byte("header")).Read(key)
+	hkdf.New(sha256.New, f.fileKey, nil, []byte("header")).Read(key)
 	h := hmac.New(sha256.New, key)
 	h.Write(f.Buf.Bytes())
 	h.Write([]byte("---"))
@@ -124,10 +131,9 @@ func (f *TestFile) HMAC() {
 }
 
 func (f *TestFile) Nonce() {
-	nonce := make([]byte, 16)
-	f.rand.Read(nonce)
+	nonce := f.Rand(16)
 	f.streamKey = make([]byte, 32)
-	hkdf.New(sha256.New, TestFileKey, nonce, []byte("payload")).Read(f.streamKey)
+	hkdf.New(sha256.New, f.fileKey, nonce, []byte("payload")).Read(f.streamKey)
 	f.Buf.Write(nonce)
 }
 
@@ -167,6 +173,7 @@ func (f *TestFile) Generate() {
 	if f.expect == "success" {
 		fmt.Printf("payload: %x\n", sha256.Sum256(f.payload.Bytes()))
 	}
+	fmt.Printf("file key: %x\n", f.fileKey)
 	for _, id := range f.identities {
 		fmt.Printf("identity: %s\n", id)
 	}
diff --git a/testdata/bad_hmac.go b/testdata/bad_hmac.go
new file mode 100644
index 0000000..15c2854
--- /dev/null
+++ b/testdata/bad_hmac.go
@@ -0,0 +1,21 @@
+// Copyright 2022 The age Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import "filippo.io/age/internal/testkit"
+
+func main() {
+	f := testkit.NewTestFile()
+	f.VersionLine("v1")
+	f.X25519(testkit.TestX25519Identity)
+	f.FileKey(f.Rand(16))
+	f.HMAC()
+	f.FileKey(testkit.TestFileKey)
+	f.Payload("age")
+	f.ExpectHeaderFailure()
+	f.Generate()
+}
diff --git a/testdata/bad_hmac.test b/testdata/bad_hmac.test
new file mode 100644
index 0000000..dd37b49
--- /dev/null
+++ b/testdata/bad_hmac.test
@@ -0,0 +1,9 @@
+expect: header failure
+file key: 59454c4c4f57205355424d4152494e45
+identity: AGE-SECRET-KEY-1EGTZVFFV20835NWYV6270LXYVK2VKNX2MMDKWYKLMGR48UAWX40Q2P2LM0
+
+age-encryption.org/v1
+-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
+hjabGXwSLQ9c3S6Lw2i+S2Tu2fiwQHHslbBN6B41FLE
+--- UGnodA32FkH3AOl9BHP6biSZTkNvu8B99I++7JpqZGk
+[æè.½Ó#ÈwÏ…=a×Yök×z©66Ú¦�âRùÛL
\ No newline at end of file
diff --git a/testdata/empty_payload.go b/testdata/empty_payload.go
new file mode 100644
index 0000000..82b4e93
--- /dev/null
+++ b/testdata/empty_payload.go
@@ -0,0 +1,18 @@
+// Copyright 2022 The age Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import "filippo.io/age/internal/testkit"
+
+func main() {
+	f := testkit.NewTestFile()
+	f.VersionLine("v1")
+	f.X25519(testkit.TestX25519Recipient)
+	f.HMAC()
+	f.Payload("")
+	f.Generate()
+}
diff --git a/testdata/empty_payload.test b/testdata/empty_payload.test
new file mode 100644
index 0000000..6ec1c83
--- /dev/null
+++ b/testdata/empty_payload.test
@@ -0,0 +1,10 @@
+expect: success
+payload: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+file key: 59454c4c4f57205355424d4152494e45
+identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
+
+age-encryption.org/v1
+-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
+EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
+--- Vn+54jqiiUCE+WZcEVY3f1sqHjlu/z1LCQ/T7Xm7qI0
+îÏbÇÎ‘´3'NhÔòùL­.OÏ>RŠA0Þ«ïC6åU
\ No newline at end of file
diff --git a/testdata/long_file_key.go b/testdata/long_file_key.go
new file mode 100644
index 0000000..df59334
--- /dev/null
+++ b/testdata/long_file_key.go
@@ -0,0 +1,21 @@
+// Copyright 2022 The age Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import "filippo.io/age/internal/testkit"
+
+func main() {
+	f := testkit.NewTestFile()
+	f.FileKey([]byte("A LONGER YELLOW SUBMARINE"))
+	f.VersionLine("v1")
+	f.X25519(testkit.TestX25519Identity)
+	f.HMAC()
+	f.Payload("age")
+	f.ExpectHeaderFailure()
+	f.Comment("the file key must be checked to be 16 bytes before decrypting it")
+	f.Generate()
+}
diff --git a/testdata/long_file_key.test b/testdata/long_file_key.test
new file mode 100644
index 0000000..30dc440
--- /dev/null
+++ b/testdata/long_file_key.test
@@ -0,0 +1,10 @@
+expect: header failure
+file key: 41204c4f4e4745522059454c4c4f57205355424d4152494e45
+identity: AGE-SECRET-KEY-1EGTZVFFV20835NWYV6270LXYVK2VKNX2MMDKWYKLMGR48UAWX40Q2P2LM0
+comment: the file key must be checked to be 16 bytes before decrypting it
+
+age-encryption.org/v1
+-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
+nlObGn0CSA4pxiaG3W6nLlaFFuHmqW+bFC6sJmbsJ9yFesgSok1K0AI
+--- C49Jo3+j4I6jWB2tldSs1jVAXbv0mOTAnwdT+5vOiBg
+îÏbÇÎ‘´3'NhÔòùLc÷(­çñ
ÐtÿÇ�P²)€x1
\ No newline at end of file
diff --git a/testdata/no_match.go b/testdata/no_match.go
new file mode 100644
index 0000000..af27c51
--- /dev/null
+++ b/testdata/no_match.go
@@ -0,0 +1,21 @@
+// Copyright 2022 The age Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import "filippo.io/age/internal/testkit"
+
+func main() {
+	f := testkit.NewTestFile()
+	f.VersionLine("v1")
+	identity := f.Rand(32)
+	f.X25519RecordIdentity(identity)
+	f.X25519NoRecordIdentity(testkit.TestX25519Recipient)
+	f.HMAC()
+	f.Payload("age")
+	f.ExpectHeaderFailure()
+	f.Generate()
+}
diff --git a/testdata/no_match.test b/testdata/no_match.test
new file mode 100644
index 0000000..c146be3
--- /dev/null
+++ b/testdata/no_match.test
@@ -0,0 +1,9 @@
+expect: header failure
+file key: 59454c4c4f57205355424d4152494e45
+identity: AGE-SECRET-KEY-143WN7DCXU4G8R5AXQSSYD9AEPYDNT3HXSLWSPK36CDU6E8M59SSSAGZ3KG
+
+age-encryption.org/v1
+-> X25519 ajtqAvDEkVNr2B7zUOtq2mAQXDSBlNrVAuM/dKb5sT4
+HUKtz0R2j5Bl2ER7HhAZrURikCFpiIjNa0KjHcjbAGU
+--- rrpTlvKEKrK3EqhoOPJeP1KE8O1d2arrRez77mwekRc
+ÝßrÐo¼«Wß=
1$–­!Œ×ýo€x»ø�-ØyG^·½^ˆ
\ No newline at end of file
diff --git a/testdata/stanza_empty_body.go b/testdata/stanza_empty_body.go
new file mode 100644
index 0000000..d5eb52d
--- /dev/null
+++ b/testdata/stanza_empty_body.go
@@ -0,0 +1,20 @@
+// Copyright 2022 The age Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import "filippo.io/age/internal/testkit"
+
+func main() {
+	f := testkit.NewTestFile()
+	f.VersionLine("v1")
+	f.X25519(testkit.TestX25519Recipient)
+	f.ArgsLine("empty")
+	f.Body([]byte(""))
+	f.HMAC()
+	f.Payload("age")
+	f.Generate()
+}
diff --git a/testdata/stanza_empty_body.test b/testdata/stanza_empty_body.test
new file mode 100644
index 0000000..cf8ec9b
--- /dev/null
+++ b/testdata/stanza_empty_body.test
@@ -0,0 +1,12 @@
+expect: success
+payload: 013f54400c82da08037759ada907a8b864e97de81c088a182062c4b5622fd2ab
+file key: 59454c4c4f57205355424d4152494e45
+identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
+
+age-encryption.org/v1
+-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
+EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
+-> empty
+
+--- 697zSC9pa/ZLNIaXGtuwcUobmxv+Dpx48Hv0papk5c0
+îÏbÇÎ‘´3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf
\ No newline at end of file
diff --git a/testdata/stanza_empty_last_line.go b/testdata/stanza_empty_last_line.go
new file mode 100644
index 0000000..519be1e
--- /dev/null
+++ b/testdata/stanza_empty_last_line.go
@@ -0,0 +1,24 @@
+// Copyright 2022 The age Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import (
+	"bytes"
+
+	"filippo.io/age/internal/testkit"
+)
+
+func main() {
+	f := testkit.NewTestFile()
+	f.VersionLine("v1")
+	f.X25519(testkit.TestX25519Recipient)
+	f.ArgsLine("stanza")
+	f.Body(bytes.Repeat([]byte("A"), 48))
+	f.HMAC()
+	f.Payload("age")
+	f.Generate()
+}
diff --git a/testdata/stanza_empty_last_line.test b/testdata/stanza_empty_last_line.test
new file mode 100644
index 0000000..285f0d8
--- /dev/null
+++ b/testdata/stanza_empty_last_line.test
@@ -0,0 +1,13 @@
+expect: success
+payload: 013f54400c82da08037759ada907a8b864e97de81c088a182062c4b5622fd2ab
+file key: 59454c4c4f57205355424d4152494e45
+identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
+
+age-encryption.org/v1
+-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
+EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
+-> stanza
+QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
+
+--- +3PC416gxa7Mk7WxpX0kb6DVfSuCun0niGre+G4bZhE
+îÏbÇÎ‘´3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf
\ No newline at end of file
diff --git a/testdata/stanza_missing_body.go b/testdata/stanza_missing_body.go
new file mode 100644
index 0000000..649b5b6
--- /dev/null
+++ b/testdata/stanza_missing_body.go
@@ -0,0 +1,22 @@
+// Copyright 2022 The age Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import "filippo.io/age/internal/testkit"
+
+func main() {
+	f := testkit.NewTestFile()
+	f.VersionLine("v1")
+	f.X25519(testkit.TestX25519Recipient)
+	f.ArgsLine("empty")
+	// Missing body.
+	f.HMAC()
+	f.Payload("age")
+	f.ExpectHeaderFailure()
+	f.Comment("every stanza must end with a short body line, even if empty")
+	f.Generate()
+}
diff --git a/testdata/stanza_missing_body.test b/testdata/stanza_missing_body.test
new file mode 100644
index 0000000..4c184c7
--- /dev/null
+++ b/testdata/stanza_missing_body.test
@@ -0,0 +1,11 @@
+expect: header failure
+file key: 59454c4c4f57205355424d4152494e45
+identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
+comment: every stanza must end with a short body line, even if empty
+
+age-encryption.org/v1
+-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
+EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
+-> empty
+--- CDgFIIJ1wE4CpW6zG+LVZ6/G/RCNTH6ZUVGp2NbeIkU
+îÏbÇÎ‘´3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf
\ No newline at end of file
diff --git a/testdata/stanza_missing_final_line.go b/testdata/stanza_missing_final_line.go
new file mode 100644
index 0000000..5b5b6b5
--- /dev/null
+++ b/testdata/stanza_missing_final_line.go
@@ -0,0 +1,26 @@
+// Copyright 2022 The age Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import (
+	"strings"
+
+	"filippo.io/age/internal/testkit"
+)
+
+func main() {
+	f := testkit.NewTestFile()
+	f.VersionLine("v1")
+	f.X25519(testkit.TestX25519Recipient)
+	f.ArgsLine("stanza")
+	f.TextLine(strings.Repeat("A", 64))
+	f.HMAC()
+	f.Payload("age")
+	f.ExpectHeaderFailure()
+	f.Comment("every stanza must end with a short body line")
+	f.Generate()
+}
diff --git a/testdata/stanza_missing_final_line.test b/testdata/stanza_missing_final_line.test
new file mode 100644
index 0000000..58774f4
--- /dev/null
+++ b/testdata/stanza_missing_final_line.test
@@ -0,0 +1,12 @@
+expect: header failure
+file key: 59454c4c4f57205355424d4152494e45
+identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
+comment: every stanza must end with a short body line
+
+age-encryption.org/v1
+-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
+EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
+-> stanza
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+--- GRjUy1ShNhFoV3cQikdtUZqDeDEZSrbtNXUgDtDbwC8
+îÏbÇÎ‘´3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf
\ No newline at end of file
diff --git a/testdata/stanza_multiple_short_lines.go b/testdata/stanza_multiple_short_lines.go
new file mode 100644
index 0000000..045f78f
--- /dev/null
+++ b/testdata/stanza_multiple_short_lines.go
@@ -0,0 +1,27 @@
+// Copyright 2022 The age Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import (
+	"strings"
+
+	"filippo.io/age/internal/testkit"
+)
+
+func main() {
+	f := testkit.NewTestFile()
+	f.VersionLine("v1")
+	f.X25519(testkit.TestX25519Recipient)
+	f.ArgsLine("stanza")
+	f.TextLine(strings.Repeat("A", 32))
+	f.TextLine(strings.Repeat("A", 32))
+	f.HMAC()
+	f.Payload("age")
+	f.ExpectHeaderFailure()
+	f.Comment("a short body line ends the stanza")
+	f.Generate()
+}
diff --git a/testdata/stanza_multiple_short_lines.test b/testdata/stanza_multiple_short_lines.test
new file mode 100644
index 0000000..e191764
--- /dev/null
+++ b/testdata/stanza_multiple_short_lines.test
@@ -0,0 +1,13 @@
+expect: header failure
+file key: 59454c4c4f57205355424d4152494e45
+identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
+comment: a short body line ends the stanza
+
+age-encryption.org/v1
+-> X25519 TEiF0ypqr+bpvcqXNyCVJpL7OuwPdVwPL7KQEbFDOCc
+EmECAEcKN+n/Vs9SbWiV+Hu0r+E8R77DdWYyd83nw7U
+-> stanza
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+--- ct87HSIMoTC4nUsQva+8AeKc2bK2q8b9sPjRhjuf1us
+îÏbÇÎ‘´3'NhÔòùL·L[þ÷¾ªRÈð¼™,ƒ1ûf
\ No newline at end of file
diff --git a/testdata/x25519.test b/testdata/x25519.test
index d0444b4..c7bb509 100644
--- a/testdata/x25519.test
+++ b/testdata/x25519.test
@@ -1,5 +1,6 @@
 expect: success
 payload: 013f54400c82da08037759ada907a8b864e97de81c088a182062c4b5622fd2ab
+file key: 59454c4c4f57205355424d4152494e45
 identity: AGE-SECRET-KEY-1XMWWC06LY3EE5RYTXM9MFLAZ2U56JJJ36S0MYPDRWSVLUL66MV4QX3S7F6
 
 age-encryption.org/v1
diff --git a/testkit_test.go b/testkit_test.go
index 01fdda9..cfc1233 100644
--- a/testkit_test.go
+++ b/testkit_test.go
@@ -9,7 +9,6 @@ import (
 	"crypto/sha256"
 	"encoding/hex"
 	"flag"
-	"fmt"
 	"io"
 	"log"
 	"os"
@@ -27,15 +26,26 @@ func TestMain(m *testing.M) {
 	genFlag := flag.Bool("generate", false, "regenerate test files")
 	flag.Parse()
 	if *genFlag {
+		log.SetFlags(0)
+		tests, err := filepath.Glob("testdata/*.test")
+		if err != nil {
+			log.Fatal(err)
+		}
+		for _, test := range tests {
+			os.Remove(test)
+		}
 		generators, err := filepath.Glob("testdata/*.go")
 		if err != nil {
 			log.Fatal(err)
 		}
 		for _, generator := range generators {
 			vector := strings.TrimSuffix(generator, ".go") + ".test"
-			fmt.Fprintf(os.Stderr, "%s -> %s\n", generator, vector)
+			log.Printf("%s -> %s\n", generator, vector)
 			out, err := exec.Command("go", "run", generator).Output()
 			if err != nil {
+				if err, ok := err.(*exec.ExitError); ok {
+					log.Fatalf("%s", err.Stderr)
+				}
 				log.Fatal(err)
 			}
 			os.WriteFile(vector, out, 0664)
@@ -104,6 +114,8 @@ func testVector(t *testing.T, test []byte) {
 				t.Fatal(err)
 			}
 			identities = append(identities, i)
+		case "file key":
+			// Ignored.
 		case "comment":
 			t.Log(value)
 		default:
@@ -114,6 +126,7 @@ func testVector(t *testing.T, test []byte) {
 	r, err := age.Decrypt(bytes.NewReader(test), identities...)
 	if err != nil {
 		if expectHeaderFailure {
+			t.Log(err)
 			return
 		}
 		t.Fatal("unexpected header error:", err)
@@ -123,6 +136,7 @@ func testVector(t *testing.T, test []byte) {
 	out, err := io.ReadAll(r)
 	if err != nil {
 		if expectPayloadFailure {
+			t.Log(err)
 			return
 		}
 		t.Fatal("unexpected payload error:", err)
