golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/task/announce_test.go (about)

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package task
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"errors"
    11  	"net/mail"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/google/go-cmp/cmp"
    19  	"golang.org/x/build/internal/workflow"
    20  )
    21  
    22  // Test that the task doesn't start running if the provided
    23  // context doesn't have sufficient time for the task to run.
    24  func TestAnnounceReleaseShortContext(t *testing.T) {
    25  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    26  	defer cancel()
    27  	_, err := (AnnounceMailTasks{}).AnnounceRelease(&workflow.TaskContext{Context: ctx}, KindMinor, []Published{{Version: "go1.18.1"}, {Version: "go1.17.8"}}, nil, nil)
    28  	if err == nil {
    29  		t.Errorf("want non-nil error")
    30  	} else if !strings.HasPrefix(err.Error(), "insufficient time") {
    31  		t.Errorf("want error that starts with 'insufficient time' instead of: %s", err)
    32  	}
    33  }
    34  
    35  func TestAnnouncementMail(t *testing.T) {
    36  	tests := [...]struct {
    37  		name        string
    38  		in          any
    39  		wantSubject string
    40  	}{
    41  		{
    42  			name: "announce-minor",
    43  			in: releaseAnnouncement{
    44  				Kind:             KindMinor,
    45  				Version:          "go1.18.1",
    46  				SecondaryVersion: "go1.17.9",
    47  				Names:            []string{"Alice", "Bob", "Charlie"},
    48  			},
    49  			wantSubject: "Go 1.18.1 and Go 1.17.9 are released",
    50  		},
    51  		{
    52  			name: "announce-minor-with-security",
    53  			in: releaseAnnouncement{
    54  				Kind:             KindMinor,
    55  				Version:          "go1.18.1",
    56  				SecondaryVersion: "go1.17.9",
    57  				Security: []string{
    58  					`encoding/pem: fix stack overflow in Decode
    59  
    60  A large (more than 5 MB) PEM input can cause a stack overflow in Decode, leading the program to crash.
    61  
    62  Thanks to Juho Nurminen of Mattermost who reported the error.
    63  
    64  This is CVE-2022-24675 and https://go.dev/issue/51853.`,
    65  					`crypto/elliptic: tolerate all oversized scalars in generic P-256
    66  
    67  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.
    68  
    69  This was discovered thanks to a Project Wycheproof test vector.
    70  
    71  This is CVE-2022-28327 and https://go.dev/issue/52075.`,
    72  					`crypto/x509: non-compliant certificates can cause a panic in Verify on macOS in Go 1.18
    73  
    74  Verifying certificate chains containing certificates which are not compliant with RFC 5280 causes Certificate.Verify to panic on macOS.
    75  
    76  These chains can be delivered through TLS and can cause a crypto/tls or net/http client to crash.
    77  
    78  Thanks to Tailscale for doing weird things and finding this.
    79  
    80  This is CVE-2022-27536 and https://go.dev/issue/51759.`,
    81  				},
    82  			},
    83  			wantSubject: "[security] Go 1.18.1 and Go 1.17.9 are released",
    84  		},
    85  		{
    86  			name: "announce-minor-solo",
    87  			in: releaseAnnouncement{
    88  				Kind:     KindMinor,
    89  				Version:  "go1.11.1",
    90  				Security: []string{"abc: security fix 1", "xyz: security fix 2"},
    91  				Names:    []string{"Alice"},
    92  			},
    93  			wantSubject: "[security] Go 1.11.1 is released",
    94  		},
    95  		{
    96  			name: "announce-beta",
    97  			in: releaseAnnouncement{
    98  				Kind:    KindBeta,
    99  				Version: "go1.19beta5",
   100  			},
   101  			wantSubject: "Go 1.19 Beta 5 is released",
   102  		},
   103  		{
   104  			name: "announce-rc",
   105  			in: releaseAnnouncement{
   106  				Kind:    KindRC,
   107  				Version: "go1.19rc6",
   108  			},
   109  			wantSubject: "Go 1.19 Release Candidate 6 is released",
   110  		},
   111  		{
   112  			name: "announce-major",
   113  			in: releaseAnnouncement{
   114  				Kind:    KindMajor,
   115  				Version: "go1.21.0",
   116  			},
   117  			wantSubject: "Go 1.21.0 is released",
   118  		},
   119  
   120  		{
   121  			name: "pre-announce-minor",
   122  			in: releasePreAnnouncement{
   123  				Target:           Date{2022, time.July, 12},
   124  				Version:          "go1.18.4",
   125  				SecondaryVersion: "go1.17.12",
   126  				Security:         "the standard library",
   127  				CVEs:             []string{"cve-1234", "cve-5678"},
   128  				Names:            []string{"Alice"},
   129  			},
   130  			wantSubject: "[security] Go 1.18.4 and Go 1.17.12 pre-announcement",
   131  		},
   132  		{
   133  			name: "pre-announce-minor-solo",
   134  			in: releasePreAnnouncement{
   135  				Target:   Date{2022, time.July, 12},
   136  				Version:  "go1.18.4",
   137  				Security: "the toolchain",
   138  				CVEs:     []string{"cve-1234", "cve-5678"},
   139  				Names:    []string{"Alice", "Bob"},
   140  			},
   141  			wantSubject: "[security] Go 1.18.4 pre-announcement",
   142  		},
   143  	}
   144  	for _, tc := range tests {
   145  		t.Run(tc.name, func(t *testing.T) {
   146  			m, err := announcementMail(tc.in)
   147  			if err != nil {
   148  				t.Fatal("announcementMail returned non-nil error:", err)
   149  			}
   150  			if *updateFlag {
   151  				writeTestdataFile(t, tc.name+".html", []byte(m.BodyHTML))
   152  				writeTestdataFile(t, tc.name+".txt", []byte(m.BodyText))
   153  				return
   154  			}
   155  			if diff := cmp.Diff(tc.wantSubject, m.Subject); diff != "" {
   156  				t.Errorf("subject mismatch (-want +got):\n%s", diff)
   157  			}
   158  			if diff := cmp.Diff(testdataFile(t, tc.name+".html"), m.BodyHTML); diff != "" {
   159  				t.Errorf("body HTML mismatch (-want +got):\n%s", diff)
   160  			}
   161  			if diff := cmp.Diff(testdataFile(t, tc.name+".txt"), m.BodyText); diff != "" {
   162  				t.Errorf("body text mismatch (-want +got):\n%s", diff)
   163  			}
   164  			if t.Failed() {
   165  				t.Log("\n\n(if the new output is intentional, use -update flag to update goldens)")
   166  			}
   167  		})
   168  	}
   169  }
   170  
   171  // testdataFile reads the named file in the testdata directory.
   172  func testdataFile(t *testing.T, name string) string {
   173  	t.Helper()
   174  	b, err := os.ReadFile(filepath.Join("testdata", name))
   175  	if err != nil {
   176  		t.Fatal(err)
   177  	}
   178  	return string(b)
   179  }
   180  
   181  // writeTestdataFile writes the named file in the testdata directory.
   182  func writeTestdataFile(t *testing.T, name string, data []byte) {
   183  	t.Helper()
   184  	err := os.WriteFile(filepath.Join("testdata", name), data, 0644)
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  }
   189  
   190  func TestAnnounceRelease(t *testing.T) {
   191  	if testing.Short() {
   192  		t.Skip("not running test that uses internet in short mode")
   193  	}
   194  
   195  	tests := [...]struct {
   196  		name         string
   197  		kind         ReleaseKind
   198  		published    []Published
   199  		security     []string
   200  		coordinators []string
   201  		want         SentMail
   202  		wantLog      string
   203  	}{
   204  		{
   205  			name:         "minor",
   206  			kind:         KindMinor,
   207  			published:    []Published{{Version: "go1.18.1"}, {Version: "go1.17.8"}}, // Intentionally not 1.17.9 so the real email doesn't get in the way.
   208  			coordinators: []string{"heschi", "dmitshur"},
   209  			want:         SentMail{Subject: "Go 1.18.1 and Go 1.17.8 are released"},
   210  			wantLog: `announcement subject: Go 1.18.1 and Go 1.17.8 are released
   211  
   212  announcement body HTML:
   213  <p>Hello gophers,</p>
   214  <p>We have just released Go versions 1.18.1 and 1.17.8, minor point releases.</p>
   215  <p>View the release notes for more information:<br>
   216  <a href="https://go.dev/doc/devel/release#go1.18.1">https://go.dev/doc/devel/release#go1.18.1</a></p>
   217  <p>You can download binary and source distributions from the Go website:<br>
   218  <a href="https://go.dev/dl/">https://go.dev/dl/</a></p>
   219  <p>To compile from source using a Git clone, update to the release with<br>
   220  <code>git checkout go1.18.1</code> and build as usual.</p>
   221  <p>Thanks to everyone who contributed to the releases.</p>
   222  <p>Cheers,<br>
   223  Heschi and Dmitri for the Go team</p>
   224  
   225  announcement body text:
   226  Hello gophers,
   227  
   228  We have just released Go versions 1.18.1 and 1.17.8, minor point releases.
   229  
   230  View the release notes for more information:
   231  https://go.dev/doc/devel/release#go1.18.1
   232  
   233  You can download binary and source distributions from the Go website:
   234  https://go.dev/dl/
   235  
   236  To compile from source using a Git clone, update to the release with
   237  git checkout go1.18.1 and build as usual.
   238  
   239  Thanks to everyone who contributed to the releases.
   240  
   241  Cheers,
   242  Heschi and Dmitri for the Go team` + "\n",
   243  		},
   244  		// Just one test case is enough, since TestAnnouncementMail
   245  		// has very thorough coverage for all release types.
   246  	}
   247  	for _, tc := range tests {
   248  		t.Run(tc.name, func(t *testing.T) {
   249  			annMail := MailHeader{
   250  				From: mail.Address{Address: "from-address@golang.test"},
   251  				To:   mail.Address{Address: "to-address@golang.test"},
   252  			}
   253  			tasks := AnnounceMailTasks{
   254  				SendMail: func(h MailHeader, c MailContent) error {
   255  					if diff := cmp.Diff(annMail, h); diff != "" {
   256  						t.Errorf("mail header mismatch (-want +got):\n%s", diff)
   257  					}
   258  					if diff := cmp.Diff(tc.want.Subject, c.Subject); diff != "" {
   259  						t.Errorf("mail subject mismatch (-want +got):\n%s", diff)
   260  					}
   261  					return nil
   262  				},
   263  				AnnounceMailHeader: annMail,
   264  			}
   265  			var buf bytes.Buffer
   266  			ctx := &workflow.TaskContext{Context: context.Background(), Logger: fmtWriter{&buf}}
   267  			sentMail, err := tasks.AnnounceRelease(ctx, tc.kind, tc.published, tc.security, tc.coordinators)
   268  			if err != nil {
   269  				if fe := (fetchError{}); errors.As(err, &fe) && fe.PossiblyRetryable {
   270  					t.Skip("test run produced no actionable signal due to a transient network error:", err) // See go.dev/issue/60541.
   271  				}
   272  				t.Fatal("task function returned non-nil error:", err)
   273  			}
   274  			if diff := cmp.Diff(tc.want, sentMail); diff != "" {
   275  				t.Errorf("sent mail mismatch (-want +got):\n%s", diff)
   276  			}
   277  			if diff := cmp.Diff(tc.wantLog, buf.String()); diff != "" {
   278  				t.Errorf("log mismatch (-want +got):\n%s", diff)
   279  			}
   280  		})
   281  	}
   282  }
   283  
   284  func TestPreAnnounceRelease(t *testing.T) {
   285  	if testing.Short() {
   286  		t.Skip("not running test that uses internet in short mode")
   287  	}
   288  
   289  	tests := [...]struct {
   290  		name         string
   291  		versions     []string
   292  		target       Date
   293  		security     string
   294  		cves         []string
   295  		coordinators []string
   296  		want         SentMail
   297  		wantLog      string
   298  	}{
   299  		{
   300  			name:         "minor",
   301  			versions:     []string{"go1.18.4", "go1.17.11"}, // Intentionally not 1.17.12 so the real email doesn't get in the way.
   302  			target:       Date{2022, time.July, 12},
   303  			security:     "the standard library",
   304  			cves:         []string{"cve-2022-1234", "cve-2023-1234"},
   305  			coordinators: []string{"tatiana"},
   306  			want:         SentMail{Subject: "[security] Go 1.18.4 and Go 1.17.11 pre-announcement"},
   307  			wantLog: `pre-announcement subject: [security] Go 1.18.4 and Go 1.17.11 pre-announcement
   308  
   309  pre-announcement body HTML:
   310  <p>Hello gophers,</p>
   311  <p>We plan to issue Go 1.18.4 and Go 1.17.11 during US business hours on Tuesday, July 12.</p>
   312  <p>These minor releases include PRIVATE security fixes to the standard library, covering the following CVEs:</p>
   313  <ul>
   314  <li>cve-2022-1234</li>
   315  <li>cve-2023-1234</li>
   316  </ul>
   317  <p>Following our security policy, this is the pre-announcement of those releases.</p>
   318  <p>Thanks,<br>
   319  Tatiana for the Go team</p>
   320  
   321  pre-announcement body text:
   322  Hello gophers,
   323  
   324  We plan to issue Go 1.18.4 and Go 1.17.11 during US business hours on Tuesday, July 12.
   325  
   326  These minor releases include PRIVATE security fixes to the standard library, covering the following CVEs:
   327  
   328  -	cve-2022-1234
   329  
   330  -	cve-2023-1234
   331  
   332  Following our security policy, this is the pre-announcement of those releases.
   333  
   334  Thanks,
   335  Tatiana for the Go team` + "\n",
   336  		},
   337  		// TestAnnouncementMail has additional coverage.
   338  	}
   339  	for _, tc := range tests {
   340  		t.Run(tc.name, func(t *testing.T) {
   341  			tasks := AnnounceMailTasks{
   342  				SendMail:    func(h MailHeader, c MailContent) error { return nil },
   343  				testHookNow: func() time.Time { return time.Date(2022, time.July, 7, 0, 0, 0, 0, time.UTC) },
   344  			}
   345  			var buf bytes.Buffer
   346  			ctx := &workflow.TaskContext{Context: context.Background(), Logger: fmtWriter{&buf}}
   347  			sentMail, err := tasks.PreAnnounceRelease(ctx, tc.versions, tc.target, tc.security, tc.cves, tc.coordinators)
   348  			if err != nil {
   349  				if fe := (fetchError{}); errors.As(err, &fe) && fe.PossiblyRetryable {
   350  					t.Skip("test run produced no actionable signal due to a transient network error:", err) // See go.dev/issue/60541.
   351  				}
   352  				t.Fatal("task function returned non-nil error:", err)
   353  			}
   354  			if diff := cmp.Diff(tc.want, sentMail); diff != "" {
   355  				t.Errorf("sent mail mismatch (-want +got):\n%s", diff)
   356  			}
   357  			if diff := cmp.Diff(tc.wantLog, buf.String()); diff != "" {
   358  				t.Errorf("log mismatch (-want +got):\n%s", diff)
   359  			}
   360  		})
   361  	}
   362  }
   363  
   364  func TestFindGoogleGroupsThread(t *testing.T) {
   365  	if testing.Short() {
   366  		t.Skip("not running test that uses internet in short mode")
   367  	}
   368  
   369  	threadURL, err := findGoogleGroupsThread(&workflow.TaskContext{
   370  		Context: context.Background(),
   371  	}, "[security] Go 1.18.3 and Go 1.17.11 are released")
   372  	if err != nil {
   373  		if fe := (fetchError{}); errors.As(err, &fe) && fe.PossiblyRetryable {
   374  			t.Skip("test run produced no actionable signal due to a transient network error:", err) // See go.dev/issue/60541.
   375  		}
   376  		t.Fatalf("findGoogleGroupsThread returned a non-nil error: %v", err)
   377  	}
   378  	// Just log the threadURL since we can't rely on stable output.
   379  	// This test is mostly for debugging if we need to.
   380  	t.Logf("threadURL: %q\n", threadURL)
   381  }
   382  
   383  func TestMarkdownToText(t *testing.T) {
   384  	const in = `Hello gophers,
   385  
   386  This is a simple Markdown document that exercises
   387  a limited set of features used in email templates.
   388  
   389  There may be security fixes following the [security policy](https://go.dev/security):
   390  
   391  -	abc: Read hangs on extremely large input
   392  
   393  	On an operating system, ` + "`Read`" + ` will hang indefinitely if
   394  	the buffer size is larger than 1 << 64 - 1 bytes.
   395  
   396  	Thanks to Gopher A for reporting the issue.
   397  
   398  	This is CVE-123 and Go issue https://go.dev/issue/123.
   399  
   400  -	xyz: Clean("X") returns "Y" when Z
   401  
   402  	Some description of the problem here.
   403  
   404  	Markdown allows one to use backslash escapes, like \_underscore\_
   405  	or \*literal asterisks\*, so we might encounter that.
   406  
   407  View release notes:
   408  https://go.dev/doc/devel/release#go1.18.3
   409  
   410  You can download binaries:
   411  https://go.dev/dl/
   412  
   413  To builds from source, use
   414  ` + "`git checkout`" + `.
   415  
   416  An easy way to try go1.19beta1
   417  is by using the go command:
   418  $ go install example.org@latest
   419  $ example download
   420  
   421  That's all for now.
   422  `
   423  	_, got, err := renderMarkdown(strings.NewReader(in))
   424  	if err != nil {
   425  		t.Fatal(err)
   426  	}
   427  
   428  	const want = `Hello gophers,
   429  
   430  This is a simple Markdown document that exercises
   431  a limited set of features used in email templates.
   432  
   433  There may be security fixes following the security policy <https://go.dev/security>:
   434  
   435  -	abc: Read hangs on extremely large input
   436  
   437  	On an operating system, Read will hang indefinitely if
   438  	the buffer size is larger than 1 << 64 - 1 bytes.
   439  
   440  	Thanks to Gopher A for reporting the issue.
   441  
   442  	This is CVE-123 and Go issue https://go.dev/issue/123.
   443  
   444  -	xyz: Clean("X") returns "Y" when Z
   445  
   446  	Some description of the problem here.
   447  
   448  	Markdown allows one to use backslash escapes, like \_underscore\_
   449  	or \*literal asterisks\*, so we might encounter that.
   450  
   451  View release notes:
   452  https://go.dev/doc/devel/release#go1.18.3
   453  
   454  You can download binaries:
   455  https://go.dev/dl/
   456  
   457  To builds from source, use
   458  git checkout.
   459  
   460  An easy way to try go1.19beta1
   461  is by using the go command:
   462  $ go install example.org@latest
   463  $ example download
   464  
   465  That's all for now.
   466  `
   467  	if diff := cmp.Diff(want, got); diff != "" {
   468  		t.Errorf("plain text rendering mismatch (-want +got):\n%s", diff)
   469  	}
   470  }