internal/task: add release announcement email templates and test data

This CL contains just the templates and test data for review
convenience. The next CL in the stack holds the implementation.

The email templates have a MIME-style header with a Subject key,
which is used to determine the email subject, and the rest is
the email body in Markdown format.

While the announcement emails have been generated from templates
for a while, there was variation in minor formatting and wording
over the years. These templates are converted to Markdown and
attempt to not to deviate from the average past announcements
too much while correcting minor inconsistencies.

Making larger changes can be considered later. It should be easier
now that this task is driven by templates without outsourcing the
responsibility of HTML formatting and editing to the human senders.

For golang/go#47405.
Fixes golang/go#47404.

Change-Id: Ie04d94b35ce1b5d111eeb1b0b076c034ba3f4dcb
Reviewed-on: https://go-review.googlesource.com/c/build/+/412754
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/internal/task/announce.go b/internal/task/announce.go
new file mode 100644
index 0000000..553af28
--- /dev/null
+++ b/internal/task/announce.go
@@ -0,0 +1,139 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package task
+
+import (
+	"embed"
+	"fmt"
+	"strings"
+	"text/template"
+
+	"golang.org/x/build/maintner/maintnerd/maintapi/version"
+)
+
+type ReleaseAnnouncement struct {
+	// Version is the Go version that has been released.
+	//
+	// The version string must use the same format as Go tags. For example:
+	// 	• "go1.17.2" for a minor Go release
+	// 	• "go1.18" for a major Go release
+	// 	• "go1.18beta1" or "go1.18rc1" for a pre-release
+	Version string
+	// SecondaryVersion is an older Go version that was also released.
+	// This only applies to minor releases. For example, "go1.16.10".
+	SecondaryVersion string
+
+	// Security is a list of descriptions, one for each distinct
+	// security fix included in this release, in Markdown format.
+	//
+	// The empty list means there are no security fixes included.
+	//
+	// This field applies only to minor releases; it is an error
+	// to try to use it another release type.
+	Security []string
+
+	// Names is an optional list of release coordinator names to
+	// include in the sign-off message.
+	Names []string
+}
+
+type mailContent struct {
+	Subject  string
+	BodyHTML string
+	BodyText string
+}
+
+// announcementMail generates the announcement email for release r.
+func announcementMail(r ReleaseAnnouncement) (mailContent, error) {
+	// Pick a template name for this type of release.
+	var name string
+	if i := strings.Index(r.Version, "beta"); i != -1 { // A beta release.
+		name = "announce-beta.md"
+	} else if i := strings.Index(r.Version, "rc"); i != -1 { // Release Candidate.
+		name = "announce-rc.md"
+	} else if strings.Count(r.Version, ".") == 1 { // Major release like "go1.X".
+		name = "announce-major.md"
+	} else if strings.Count(r.Version, ".") == 2 { // Minor release like "go1.X.Y".
+		name = "announce-minor.md"
+	} else {
+		return mailContent{}, fmt.Errorf("unknown version format: %q", r.Version)
+	}
+
+	if len(r.Security) > 0 && name != "announce-minor.md" {
+		// The Security field isn't supported in templates other than minor,
+		// so report an error instead of silently dropping it.
+		//
+		// Note: Maybe in the future we'd want to consider support for including sentences like
+		// "This beta release includes the same security fixes as in Go X.Y.Z and Go A.B.C.",
+		// but we'll have a better idea after these initial templates get more practical use.
+		return mailContent{}, fmt.Errorf("email template %q doesn't support the Security field; this field can only be used in minor releases", name)
+	}
+
+	// TODO(go.dev/issue/47405): Render the announcement template.
+	// Get the email subject.
+	// Render the email body.
+	return mailContent{}, fmt.Errorf("not implemented yet")
+}
+
+// announceTmpl holds templates for Go release announcement emails.
+//
+// Each email template starts with a MIME-style header with a Subject key,
+// and the rest of it is Markdown for the email body.
+var announceTmpl = template.Must(template.New("").Funcs(template.FuncMap{
+	"join": func(s []string) string {
+		switch len(s) {
+		case 0:
+			return ""
+		case 1:
+			return s[0]
+		case 2:
+			return s[0] + " and " + s[1]
+		default:
+			return strings.Join(s[:len(s)-1], ", ") + ", and " + s[len(s)-1]
+		}
+	},
+	"indent": func(s string) string { return "\t" + strings.ReplaceAll(s, "\n", "\n\t") },
+
+	// subjectPrefix returns the email subject prefix for release r, if any.
+	"subjectPrefix": func(r ReleaseAnnouncement) string {
+		switch {
+		case len(r.Security) > 0:
+			// Include a security prefix as documented at https://go.dev/security#receiving-security-updates:
+			//
+			//	> The best way to receive security announcements is to subscribe to the golang-announce mailing list.
+			//	> Any messages pertaining to a security issue will be prefixed with [security].
+			//
+			return "[security]"
+		default:
+			return ""
+		}
+	},
+
+	// short and helpers below manipulate valid Go version strings
+	// for the current needs of the announcement templates.
+	"short": func(v string) string { return strings.TrimPrefix(v, "go") },
+	// major extracts the major part of a valid Go version.
+	// For example, major("go1.18.4") == "1.18".
+	"major": func(v string) (string, error) {
+		x, ok := version.Go1PointX(v)
+		if !ok {
+			return "", fmt.Errorf("internal error: version.Go1PointX(%q) is not ok", v)
+		}
+		return fmt.Sprintf("1.%d", x), nil
+	},
+	// build extracts the pre-release build number of a valid Go version.
+	// For example, build("go1.19beta2") == "2".
+	"build": func(v string) (string, error) {
+		if i := strings.Index(v, "beta"); i != -1 {
+			return v[i+len("beta"):], nil
+		} else if i := strings.Index(v, "rc"); i != -1 {
+			return v[i+len("rc"):], nil
+		}
+		return "", fmt.Errorf("internal error: unhandled pre-release Go version %q", v)
+	},
+}).ParseFS(tmplDir, "template/announce-*.md"))
+
+//go:embed template
+var tmplDir embed.FS
diff --git a/internal/task/announce_test.go b/internal/task/announce_test.go
new file mode 100644
index 0000000..fc90ee1
--- /dev/null
+++ b/internal/task/announce_test.go
@@ -0,0 +1,130 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package task
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestAnnouncementMail(t *testing.T) {
+	tests := [...]struct {
+		name        string
+		in          ReleaseAnnouncement
+		wantSubject string
+	}{
+		{
+			name: "minor",
+			in: ReleaseAnnouncement{
+				Version:          "go1.18.1",
+				SecondaryVersion: "go1.17.9",
+				Names:            []string{"Alice", "Bob", "Charlie"},
+			},
+			wantSubject: "Go 1.18.1 and Go 1.17.9 are released",
+		},
+		{
+			name: "minor-with-security",
+			in: ReleaseAnnouncement{
+				Version:          "go1.18.1",
+				SecondaryVersion: "go1.17.9",
+				Security: []string{
+					`encoding/pem: fix stack overflow in Decode
+
+A large (more than 5 MB) PEM input can cause a stack overflow in Decode, leading the program to crash.
+
+Thanks to Juho Nurminen of Mattermost who reported the error.
+
+This is CVE-2022-24675 and https://go.dev/issue/51853.`,
+					`crypto/elliptic: tolerate all oversized scalars in generic P-256
+
+A crafted scalar input longer than 32 bytes can cause P256().ScalarMult or P256().ScalarBaseMult to panic. Indirect uses through crypto/ecdsa and crypto/tls are unaffected. amd64, arm64, ppc64le, and s390x are unaffected.
+
+This was discovered thanks to a Project Wycheproof test vector.
+
+This is CVE-2022-28327 and https://go.dev/issue/52075.`,
+					`crypto/x509: non-compliant certificates can cause a panic in Verify on macOS in Go 1.18
+
+Verifying certificate chains containing certificates which are not compliant with RFC 5280 causes Certificate.Verify to panic on macOS.
+
+These chains can be delivered through TLS and can cause a crypto/tls or net/http client to crash.
+
+Thanks to Tailscale for doing weird things and finding this.
+
+This is CVE-2022-27536 and https://go.dev/issue/51759.`,
+				},
+			},
+			wantSubject: "[security] Go 1.18.1 and Go 1.17.9 are released",
+		},
+		{
+			name: "beta",
+			in: ReleaseAnnouncement{
+				Version: "go1.19beta5",
+			},
+			wantSubject: "Go 1.19 Beta 5 is released",
+		},
+		{
+			name: "rc",
+			in: ReleaseAnnouncement{
+				Version: "go1.19rc6",
+			},
+			wantSubject: "Go 1.19 Release Candidate 6 is released",
+		},
+		{
+			name: "major",
+			in: ReleaseAnnouncement{
+				Version: "go1.19",
+			},
+			wantSubject: "Go 1.19 is released",
+		},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			m, err := announcementMail(tc.in)
+			if err != nil {
+				t.Skip("announcementMail is not implemented yet") // TODO(go.dev/issue/47405): Implement.
+				t.Fatal("announcementMail returned non-nil error:", err)
+			}
+			if *updateFlag {
+				writeTestdataFile(t, "announce-"+tc.name+".html", []byte(m.BodyHTML))
+				writeTestdataFile(t, "announce-"+tc.name+".txt", []byte(m.BodyText))
+				return
+			}
+			if diff := cmp.Diff(tc.wantSubject, m.Subject); diff != "" {
+				t.Errorf("subject mismatch (-want +got):\n%s", diff)
+			}
+			if diff := cmp.Diff(testdataFile(t, "announce-"+tc.name+".html"), m.BodyHTML); diff != "" {
+				t.Errorf("body HTML mismatch (-want +got):\n%s", diff)
+			}
+			if diff := cmp.Diff(testdataFile(t, "announce-"+tc.name+".txt"), m.BodyText); diff != "" {
+				t.Errorf("body text mismatch (-want +got):\n%s", diff)
+			}
+			if t.Failed() {
+				t.Log("\n\n(if the new output is intentional, use -update flag to update goldens)")
+			}
+		})
+	}
+}
+
+// testdataFile reads the named file in the testdata directory.
+func testdataFile(t *testing.T, name string) string {
+	t.Helper()
+	b, err := os.ReadFile(filepath.Join("testdata", name))
+	if err != nil {
+		t.Fatal(err)
+	}
+	return string(b)
+}
+
+// writeTestdataFile writes the named file in the testdata directory.
+func writeTestdataFile(t *testing.T, name string, data []byte) {
+	t.Helper()
+	err := os.WriteFile(filepath.Join("testdata", name), data, 0644)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/internal/task/template/announce-beta.md b/internal/task/template/announce-beta.md
new file mode 100644
index 0000000..324d45f
--- /dev/null
+++ b/internal/task/template/announce-beta.md
@@ -0,0 +1,26 @@
+Subject: Go {{.Version|major}} Beta {{.Version|build}} is released
+
+Hello gophers,
+
+We have just released {{.Version}}, a beta version of Go {{.Version|major}}.
+It is cut from the master branch at the revision tagged {{.Version}}.
+
+Please try your production load tests and unit tests with the new version.
+Your help testing these pre-release versions is invaluable.
+
+Report any problems using the issue tracker:
+https://go.dev/issue/new
+
+If you have Go installed already, an easy way to try {{.Version}}
+is by using the go command:
+$ go install golang.org/dl/{{.Version}}@latest
+$ {{.Version}} download
+
+You can download binary and source distributions from the usual place:
+https://go.dev/dl/#{{.Version}}
+
+To find out what has changed in Go {{.Version|major}}, read the draft release notes:
+https://tip.golang.org/doc/go{{.Version|major}}
+
+Cheers,
+{{with .Names}}{{join .}} for the{{else}}The{{end}} Go team
diff --git a/internal/task/template/announce-major.md b/internal/task/template/announce-major.md
new file mode 100644
index 0000000..06f6f63
--- /dev/null
+++ b/internal/task/template/announce-major.md
@@ -0,0 +1,24 @@
+Subject: Go {{short .Version}} is released
+
+Hello gophers,
+
+We have just released Go {{short .Version}}.
+
+To find out what has changed in Go {{short .Version}}, read the release notes:
+https://go.dev/doc/{{.Version}}
+
+You can download binary and source distributions from our download page:
+https://go.dev/dl/#{{.Version}}
+
+If you have Go installed already, an easy way to try {{.Version}}
+is by using the go command:
+$ go install golang.org/dl/{{.Version}}@latest
+$ {{.Version}} download
+
+To compile from source using a Git clone, update to the release with
+`git checkout {{.Version}}` and build as usual.
+
+Thanks to everyone who contributed to the release!
+
+Cheers,
+{{with .Names}}{{join .}} for the{{else}}The{{end}} Go team
diff --git a/internal/task/template/announce-minor.md b/internal/task/template/announce-minor.md
new file mode 100644
index 0000000..5cadf4c
--- /dev/null
+++ b/internal/task/template/announce-minor.md
@@ -0,0 +1,26 @@
+Subject: {{subjectPrefix .}}
+	Go {{short .Version}} and Go {{short .SecondaryVersion}} are released
+
+Hello gophers,
+
+We have just released Go versions {{short .Version}} and {{short .SecondaryVersion}}, minor point releases.
+
+{{with .Security}}These minor releases include {{len .}} security fixes following the [security policy](https://go.dev/security):
+{{range .}}
+-{{indent .}}
+{{end}}
+{{end -}}
+
+View the release notes for more information:
+https://go.dev/doc/devel/release#{{.Version}}
+
+You can download binary and source distributions from the Go website:
+https://go.dev/dl/
+
+To compile from source using a Git clone, update to the release with
+`git checkout {{.Version}}` and build as usual.
+
+Thanks to everyone who contributed to the releases.
+
+Cheers,
+{{with .Names}}{{join .}} for the{{else}}The{{end}} Go team
diff --git a/internal/task/template/announce-rc.md b/internal/task/template/announce-rc.md
new file mode 100644
index 0000000..6833c49
--- /dev/null
+++ b/internal/task/template/announce-rc.md
@@ -0,0 +1,26 @@
+Subject: Go {{.Version|major}} Release Candidate {{.Version|build}} is released
+
+Hello gophers,
+
+We have just released {{.Version}}, a release candidate version of Go {{.Version|major}}.
+It is cut from release-branch.go{{.Version|major}} at the revision tagged {{.Version}}.
+
+Please try your production load tests and unit tests with the new version.
+Your help testing these pre-release versions is invaluable.
+
+Report any problems using the issue tracker:
+https://go.dev/issue/new
+
+If you have Go installed already, an easy way to try {{.Version}}
+is by using the go command:
+$ go install golang.org/dl/{{.Version}}@latest
+$ {{.Version}} download
+
+You can download binary and source distributions from the usual place:
+https://go.dev/dl/#{{.Version}}
+
+To find out what has changed in Go {{.Version|major}}, read the draft release notes:
+https://tip.golang.org/doc/go{{.Version|major}}
+
+Cheers,
+{{with .Names}}{{join .}} for the{{else}}The{{end}} Go team
diff --git a/internal/task/testdata/announce-beta.html b/internal/task/testdata/announce-beta.html
new file mode 100644
index 0000000..7cd0998
--- /dev/null
+++ b/internal/task/testdata/announce-beta.html
@@ -0,0 +1,17 @@
+<p>Hello gophers,</p>
+<p>We have just released go1.19beta5, a beta version of Go 1.19.<br>
+It is cut from the master branch at the revision tagged go1.19beta5.</p>
+<p>Please try your production load tests and unit tests with the new version.<br>
+Your help testing these pre-release versions is invaluable.</p>
+<p>Report any problems using the issue tracker:<br>
+<a href="https://go.dev/issue/new">https://go.dev/issue/new</a></p>
+<p>If you have Go installed already, an easy way to try go1.19beta5<br>
+is by using the go command:<br>
+$ go install golang.org/dl/go1.19beta5@latest<br>
+$ go1.19beta5 download</p>
+<p>You can download binary and source distributions from the usual place:<br>
+<a href="https://go.dev/dl/#go1.19beta5">https://go.dev/dl/#go1.19beta5</a></p>
+<p>To find out what has changed in Go 1.19, read the draft release notes:<br>
+<a href="https://tip.golang.org/doc/go1.19">https://tip.golang.org/doc/go1.19</a></p>
+<p>Cheers,<br>
+The Go team</p>
diff --git a/internal/task/testdata/announce-beta.txt b/internal/task/testdata/announce-beta.txt
new file mode 100644
index 0000000..3501492
--- /dev/null
+++ b/internal/task/testdata/announce-beta.txt
@@ -0,0 +1,24 @@
+Hello gophers,
+
+We have just released go1.19beta5, a beta version of Go 1.19.
+It is cut from the master branch at the revision tagged go1.19beta5.
+
+Please try your production load tests and unit tests with the new version.
+Your help testing these pre-release versions is invaluable.
+
+Report any problems using the issue tracker:
+https://go.dev/issue/new
+
+If you have Go installed already, an easy way to try go1.19beta5
+is by using the go command:
+$ go install golang.org/dl/go1.19beta5@latest
+$ go1.19beta5 download
+
+You can download binary and source distributions from the usual place:
+https://go.dev/dl/#go1.19beta5
+
+To find out what has changed in Go 1.19, read the draft release notes:
+https://tip.golang.org/doc/go1.19
+
+Cheers,
+The Go team
diff --git a/internal/task/testdata/announce-major.html b/internal/task/testdata/announce-major.html
new file mode 100644
index 0000000..6ae2735
--- /dev/null
+++ b/internal/task/testdata/announce-major.html
@@ -0,0 +1,15 @@
+<p>Hello gophers,</p>
+<p>We have just released Go 1.19.</p>
+<p>To find out what has changed in Go 1.19, read the release notes:<br>
+<a href="https://go.dev/doc/go1.19">https://go.dev/doc/go1.19</a></p>
+<p>You can download binary and source distributions from our download page:<br>
+<a href="https://go.dev/dl/#go1.19">https://go.dev/dl/#go1.19</a></p>
+<p>If you have Go installed already, an easy way to try go1.19<br>
+is by using the go command:<br>
+$ go install golang.org/dl/go1.19@latest<br>
+$ go1.19 download</p>
+<p>To compile from source using a Git clone, update to the release with<br>
+<code>git checkout go1.19</code> and build as usual.</p>
+<p>Thanks to everyone who contributed to the release!</p>
+<p>Cheers,<br>
+The Go team</p>
diff --git a/internal/task/testdata/announce-major.txt b/internal/task/testdata/announce-major.txt
new file mode 100644
index 0000000..f48c191
--- /dev/null
+++ b/internal/task/testdata/announce-major.txt
@@ -0,0 +1,22 @@
+Hello gophers,
+
+We have just released Go 1.19.
+
+To find out what has changed in Go 1.19, read the release notes:
+https://go.dev/doc/go1.19
+
+You can download binary and source distributions from our download page:
+https://go.dev/dl/#go1.19
+
+If you have Go installed already, an easy way to try go1.19
+is by using the go command:
+$ go install golang.org/dl/go1.19@latest
+$ go1.19 download
+
+To compile from source using a Git clone, update to the release with
+git checkout go1.19 and build as usual.
+
+Thanks to everyone who contributed to the release!
+
+Cheers,
+The Go team
diff --git a/internal/task/testdata/announce-minor-with-security.html b/internal/task/testdata/announce-minor-with-security.html
new file mode 100644
index 0000000..703026a
--- /dev/null
+++ b/internal/task/testdata/announce-minor-with-security.html
@@ -0,0 +1,33 @@
+<p>Hello gophers,</p>
+<p>We have just released Go versions 1.18.1 and 1.17.9, minor point releases.</p>
+<p>These minor releases include 3 security fixes following the <a href="https://go.dev/security">security policy</a>:</p>
+<ul>
+<li>
+<p>encoding/pem: fix stack overflow in Decode</p>
+<p>A large (more than 5 MB) PEM input can cause a stack overflow in Decode, leading the program to crash.</p>
+<p>Thanks to Juho Nurminen of Mattermost who reported the error.</p>
+<p>This is CVE-2022-24675 and <a href="https://go.dev/issue/51853">https://go.dev/issue/51853</a>.</p>
+</li>
+<li>
+<p>crypto/elliptic: tolerate all oversized scalars in generic P-256</p>
+<p>A crafted scalar input longer than 32 bytes can cause P256().ScalarMult or P256().ScalarBaseMult to panic. Indirect uses through crypto/ecdsa and crypto/tls are unaffected. amd64, arm64, ppc64le, and s390x are unaffected.</p>
+<p>This was discovered thanks to a Project Wycheproof test vector.</p>
+<p>This is CVE-2022-28327 and <a href="https://go.dev/issue/52075">https://go.dev/issue/52075</a>.</p>
+</li>
+<li>
+<p>crypto/x509: non-compliant certificates can cause a panic in Verify on macOS in Go 1.18</p>
+<p>Verifying certificate chains containing certificates which are not compliant with RFC 5280 causes Certificate.Verify to panic on macOS.</p>
+<p>These chains can be delivered through TLS and can cause a crypto/tls or net/http client to crash.</p>
+<p>Thanks to Tailscale for doing weird things and finding this.</p>
+<p>This is CVE-2022-27536 and <a href="https://go.dev/issue/51759">https://go.dev/issue/51759</a>.</p>
+</li>
+</ul>
+<p>View the release notes for more information:<br>
+<a href="https://go.dev/doc/devel/release#go1.18.1">https://go.dev/doc/devel/release#go1.18.1</a></p>
+<p>You can download binary and source distributions from the Go website:<br>
+<a href="https://go.dev/dl/">https://go.dev/dl/</a></p>
+<p>To compile from source using a Git clone, update to the release with<br>
+<code>git checkout go1.18.1</code> and build as usual.</p>
+<p>Thanks to everyone who contributed to the releases.</p>
+<p>Cheers,<br>
+The Go team</p>
diff --git a/internal/task/testdata/announce-minor-with-security.txt b/internal/task/testdata/announce-minor-with-security.txt
new file mode 100644
index 0000000..87286e8
--- /dev/null
+++ b/internal/task/testdata/announce-minor-with-security.txt
@@ -0,0 +1,45 @@
+Hello gophers,
+
+We have just released Go versions 1.18.1 and 1.17.9, minor point releases.
+
+These minor releases include 3 security fixes following the security policy <https://go.dev/security>:
+
+-	encoding/pem: fix stack overflow in Decode
+
+	A large (more than 5 MB) PEM input can cause a stack overflow in Decode, leading the program to crash.
+
+	Thanks to Juho Nurminen of Mattermost who reported the error.
+
+	This is CVE-2022-24675 and https://go.dev/issue/51853.
+
+-	crypto/elliptic: tolerate all oversized scalars in generic P-256
+
+	A crafted scalar input longer than 32 bytes can cause P256().ScalarMult or P256().ScalarBaseMult to panic. Indirect uses through crypto/ecdsa and crypto/tls are unaffected. amd64, arm64, ppc64le, and s390x are unaffected.
+
+	This was discovered thanks to a Project Wycheproof test vector.
+
+	This is CVE-2022-28327 and https://go.dev/issue/52075.
+
+-	crypto/x509: non-compliant certificates can cause a panic in Verify on macOS in Go 1.18
+
+	Verifying certificate chains containing certificates which are not compliant with RFC 5280 causes Certificate.Verify to panic on macOS.
+
+	These chains can be delivered through TLS and can cause a crypto/tls or net/http client to crash.
+
+	Thanks to Tailscale for doing weird things and finding this.
+
+	This is CVE-2022-27536 and https://go.dev/issue/51759.
+
+View the release notes for more information:
+https://go.dev/doc/devel/release#go1.18.1
+
+You can download binary and source distributions from the Go website:
+https://go.dev/dl/
+
+To compile from source using a Git clone, update to the release with
+git checkout go1.18.1 and build as usual.
+
+Thanks to everyone who contributed to the releases.
+
+Cheers,
+The Go team
diff --git a/internal/task/testdata/announce-minor.html b/internal/task/testdata/announce-minor.html
new file mode 100644
index 0000000..fafd57c
--- /dev/null
+++ b/internal/task/testdata/announce-minor.html
@@ -0,0 +1,11 @@
+<p>Hello gophers,</p>
+<p>We have just released Go versions 1.18.1 and 1.17.9, minor point releases.</p>
+<p>View the release notes for more information:<br>
+<a href="https://go.dev/doc/devel/release#go1.18.1">https://go.dev/doc/devel/release#go1.18.1</a></p>
+<p>You can download binary and source distributions from the Go website:<br>
+<a href="https://go.dev/dl/">https://go.dev/dl/</a></p>
+<p>To compile from source using a Git clone, update to the release with<br>
+<code>git checkout go1.18.1</code> and build as usual.</p>
+<p>Thanks to everyone who contributed to the releases.</p>
+<p>Cheers,<br>
+Alice, Bob, and Charlie for the Go team</p>
diff --git a/internal/task/testdata/announce-minor.txt b/internal/task/testdata/announce-minor.txt
new file mode 100644
index 0000000..bdb7fd4
--- /dev/null
+++ b/internal/task/testdata/announce-minor.txt
@@ -0,0 +1,17 @@
+Hello gophers,
+
+We have just released Go versions 1.18.1 and 1.17.9, minor point releases.
+
+View the release notes for more information:
+https://go.dev/doc/devel/release#go1.18.1
+
+You can download binary and source distributions from the Go website:
+https://go.dev/dl/
+
+To compile from source using a Git clone, update to the release with
+git checkout go1.18.1 and build as usual.
+
+Thanks to everyone who contributed to the releases.
+
+Cheers,
+Alice, Bob, and Charlie for the Go team
diff --git a/internal/task/testdata/announce-rc.html b/internal/task/testdata/announce-rc.html
new file mode 100644
index 0000000..62f03f1
--- /dev/null
+++ b/internal/task/testdata/announce-rc.html
@@ -0,0 +1,17 @@
+<p>Hello gophers,</p>
+<p>We have just released go1.19rc6, a release candidate version of Go 1.19.<br>
+It is cut from release-branch.go1.19 at the revision tagged go1.19rc6.</p>
+<p>Please try your production load tests and unit tests with the new version.<br>
+Your help testing these pre-release versions is invaluable.</p>
+<p>Report any problems using the issue tracker:<br>
+<a href="https://go.dev/issue/new">https://go.dev/issue/new</a></p>
+<p>If you have Go installed already, an easy way to try go1.19rc6<br>
+is by using the go command:<br>
+$ go install golang.org/dl/go1.19rc6@latest<br>
+$ go1.19rc6 download</p>
+<p>You can download binary and source distributions from the usual place:<br>
+<a href="https://go.dev/dl/#go1.19rc6">https://go.dev/dl/#go1.19rc6</a></p>
+<p>To find out what has changed in Go 1.19, read the draft release notes:<br>
+<a href="https://tip.golang.org/doc/go1.19">https://tip.golang.org/doc/go1.19</a></p>
+<p>Cheers,<br>
+The Go team</p>
diff --git a/internal/task/testdata/announce-rc.txt b/internal/task/testdata/announce-rc.txt
new file mode 100644
index 0000000..d61a363
--- /dev/null
+++ b/internal/task/testdata/announce-rc.txt
@@ -0,0 +1,24 @@
+Hello gophers,
+
+We have just released go1.19rc6, a release candidate version of Go 1.19.
+It is cut from release-branch.go1.19 at the revision tagged go1.19rc6.
+
+Please try your production load tests and unit tests with the new version.
+Your help testing these pre-release versions is invaluable.
+
+Report any problems using the issue tracker:
+https://go.dev/issue/new
+
+If you have Go installed already, an easy way to try go1.19rc6
+is by using the go command:
+$ go install golang.org/dl/go1.19rc6@latest
+$ go1.19rc6 download
+
+You can download binary and source distributions from the usual place:
+https://go.dev/dl/#go1.19rc6
+
+To find out what has changed in Go 1.19, read the draft release notes:
+https://tip.golang.org/doc/go1.19
+
+Cheers,
+The Go team