blob: 68bbdf121b16a70124a0520c7b8b0e34696b093b [file] [log] [blame]
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -04001// 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
Dmitri Shuralyovc9fe2cc2021-10-26 16:15:38 -04005package task
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -04006
7import (
8 "bytes"
9 "context"
10 "fmt"
11 "io"
Dmitri Shuralyovc9fe2cc2021-10-26 16:15:38 -040012 "net/http"
13 "net/http/httptest"
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -040014 "testing"
15
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -040016 "golang.org/x/build/internal/workflow"
17)
18
19func TestTweetRelease(t *testing.T) {
20 if testing.Short() {
21 // This test is useful when modifying the tweet text and image templates,
22 // but don't run it in -short mode since tweetImage involves making some
23 // HTTP GET requests to the internet.
Dmitri Shuralyovdec3b032021-12-02 20:20:26 -050024 t.Skip("skipping test that hits go.dev/dl/?mode=json read-only API in -short mode")
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -040025 }
26
27 tests := [...]struct {
28 name string
Dmitri Shuralyovc9fe2cc2021-10-26 16:15:38 -040029 taskFn func(workflow.TaskContext, ReleaseTweet, bool) (string, error)
30 in ReleaseTweet
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -040031 wantLog string
32 }{
33 {
34 name: "minor",
Dmitri Shuralyovc9fe2cc2021-10-26 16:15:38 -040035 taskFn: TweetMinorRelease,
36 in: ReleaseTweet{
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -040037 Version: "go1.17.1",
38 SecondaryVersion: "go1.16.8",
39 Security: "Includes security fixes for A and B.",
40 Announcement: "https://groups.google.com/g/golang-announce/c/dx9d7IOseHw/m/KNH37k37AAAJ",
41 RandomSeed: 234,
42 },
43 wantLog: `tweet text:
44🎊 Go 1.17.1 and 1.16.8 are released!
45
46🔐 Security: Includes security fixes for A and B.
47
48📢 Announcement: https://groups.google.com/g/golang-announce/c/dx9d7IOseHw/m/KNH37k37AAAJ
49
Dmitri Shuralyovdec3b032021-12-02 20:20:26 -050050⬇️ Download: https://go.dev/dl/#go1.17.1
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -040051
52#golang
53tweet image:
54$ go install golang.org/dl/go1.17.1@latest
55$ go1.17.1 download
56Downloaded 0.0% ( 0 / 102606384 bytes) ...
57Downloaded 50.0% ( 51303192 / 102606384 bytes) ...
58Downloaded 100.0% (102606384 / 102606384 bytes)
59Unpacking go1.17.1.linux-arm64.tar.gz ...
60Success. You may now run 'go1.17.1'
61$ go1.17.1 version
62go version go1.17.1 linux/arm64` + "\n",
63 },
64 {
65 name: "beta",
Dmitri Shuralyovc9fe2cc2021-10-26 16:15:38 -040066 taskFn: TweetBetaRelease,
67 in: ReleaseTweet{
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -040068 Version: "go1.17beta1",
69 Announcement: "https://groups.google.com/g/golang-announce/c/i4EliPDV9Ok/m/MxA-nj53AAAJ",
70 RandomSeed: 678,
71 },
72 wantLog: `tweet text:
73⚡️ Go 1.17 Beta 1 is released!
74
Dmitri Shuralyovdec3b032021-12-02 20:20:26 -050075⚙️ Try it! File bugs! https://go.dev/issue/new
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -040076
77🗣 Announcement: https://groups.google.com/g/golang-announce/c/i4EliPDV9Ok/m/MxA-nj53AAAJ
78
Dmitri Shuralyovdec3b032021-12-02 20:20:26 -050079📦 Download: https://go.dev/dl/#go1.17beta1
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -040080
81#golang
82tweet image:
83$ go install golang.org/dl/go1.17beta1@latest
84$ go1.17beta1 download
85Downloaded 0.0% ( 0 / 135610703 bytes) ...
86Downloaded 50.0% ( 67805351 / 135610703 bytes) ...
87Downloaded 100.0% (135610703 / 135610703 bytes)
88Unpacking go1.17beta1.darwin-amd64.tar.gz ...
89Success. You may now run 'go1.17beta1'
90$ go1.17beta1 version
91go version go1.17beta1 darwin/amd64` + "\n",
92 },
93 {
94 name: "rc",
Dmitri Shuralyovc9fe2cc2021-10-26 16:15:38 -040095 taskFn: TweetRCRelease,
96 in: ReleaseTweet{
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -040097 Version: "go1.17rc2",
98 Announcement: "https://groups.google.com/g/golang-announce/c/yk30ovJGXWY/m/p9uUnKbbBQAJ",
99 RandomSeed: 456,
100 },
101 wantLog: `tweet text:
102🎉 Go 1.17 Release Candidate 2 is released!
103
Dmitri Shuralyovdec3b032021-12-02 20:20:26 -0500104🏖 Run it in dev! Run it in prod! File bugs! https://go.dev/issue/new
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -0400105
106🔈 Announcement: https://groups.google.com/g/golang-announce/c/yk30ovJGXWY/m/p9uUnKbbBQAJ
107
Dmitri Shuralyovdec3b032021-12-02 20:20:26 -0500108📦 Download: https://go.dev/dl/#go1.17rc2
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -0400109
110#golang
111tweet image:
112$ go install golang.org/dl/go1.17rc2@latest
113$ go1.17rc2 download
114Downloaded 0.0% ( 0 / 116660997 bytes) ...
115Downloaded 50.0% ( 58330498 / 116660997 bytes) ...
116Downloaded 100.0% (116660997 / 116660997 bytes)
117Unpacking go1.17rc2.windows-arm64.zip ...
118Success. You may now run 'go1.17rc2'
119$ go1.17rc2 version
120go version go1.17rc2 windows/arm64` + "\n",
121 },
122 {
123 name: "major",
Dmitri Shuralyovc9fe2cc2021-10-26 16:15:38 -0400124 taskFn: TweetMajorRelease,
125 in: ReleaseTweet{
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -0400126 Version: "go1.17",
127 Security: "Includes a super duper security fix (CVE-123).",
128 RandomSeed: 123,
129 },
130 wantLog: `tweet text:
131🥳 Go go1.17 is released!
132
133🔐 Security: Includes a super duper security fix (CVE-123).
134
Dmitri Shuralyovdec3b032021-12-02 20:20:26 -0500135📝 Release notes: https://go.dev/doc/go1.17
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -0400136
Dmitri Shuralyovdec3b032021-12-02 20:20:26 -0500137📦 Download: https://go.dev/dl/#go1.17
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -0400138
139#golang
140tweet image:
141$ go install golang.org/dl/go1.17@latest
142$ go1.17 download
143Downloaded 0.0% ( 0 / 133579378 bytes) ...
144Downloaded 50.0% ( 66789689 / 133579378 bytes) ...
145Downloaded 100.0% (133579378 / 133579378 bytes)
146Unpacking go1.17.freebsd-amd64.tar.gz ...
147Success. You may now run 'go1.17'
148$ go1.17 version
149go version go1.17 freebsd/amd64` + "\n",
150 },
151 }
152 for _, tc := range tests {
153 t.Run(tc.name, func(t *testing.T) {
154 // Call the tweet task function in dry-run mode so it
155 // doesn't actually try to tweet, but capture its log.
156 var buf bytes.Buffer
157 ctx := workflow.TaskContext{Context: context.Background(), Logger: fmtWriter{&buf}}
158 tweetURL, err := tc.taskFn(ctx, tc.in, true)
159 if err != nil {
160 t.Fatal("got a non-nil error:", err)
161 }
162 if got, want := tweetURL, "(dry-run)"; got != want {
163 t.Errorf("unexpected tweetURL: got = %q, want %q", got, want)
164 }
165 if got, want := buf.String(), tc.wantLog; got != want {
Dmitri Shuralyovdec3b032021-12-02 20:20:26 -0500166 t.Errorf("unexpected log:\n got: %q\nwant: %q", got, want)
Dmitri Shuralyov46f6bfb2021-10-26 16:07:01 -0400167 }
168 })
169 }
170}
171
172type fmtWriter struct{ w io.Writer }
173
174func (f fmtWriter) Printf(format string, v ...interface{}) {
175 fmt.Fprintf(f.w, format, v...)
176}
Dmitri Shuralyovc9fe2cc2021-10-26 16:15:38 -0400177
178func TestPostTweet(t *testing.T) {
179 mux := http.NewServeMux()
180 mux.HandleFunc("upload.twitter.com/1.1/media/upload.json", func(w http.ResponseWriter, req *http.Request) {
181 if got, want := req.Method, http.MethodPost; got != want {
182 t.Errorf("media/upload: got method %s, want %s", got, want)
183 return
184 }
185 if got, want := req.FormValue("media_category"), "tweet_image"; got != want {
186 t.Errorf("media/upload: got media_category=%q, want %q", got, want)
187 }
188 f, hdr, err := req.FormFile("media")
189 if err != nil {
190 t.Errorf("media/upload: error getting image file: %v", err)
191 return
192 }
193 if got, want := hdr.Filename, "image.png"; got != want {
194 t.Errorf("media/upload: got file name=%q, want %q", got, want)
195 }
196 if got, want := mustRead(f), "image-png-bytes"; got != want {
197 t.Errorf("media/upload: got file content=%q, want %q", got, want)
198 return
199 }
200 mustWrite(w, `{"media_id_string": "media-123"}`)
201 })
202 mux.HandleFunc("api.twitter.com/1.1/statuses/update.json", func(w http.ResponseWriter, req *http.Request) {
203 if got, want := req.Method, http.MethodPost; got != want {
204 t.Errorf("statuses/update: got method %s, want %s", got, want)
205 return
206 }
207 if got, want := req.FormValue("status"), "tweet-text"; got != want {
208 t.Errorf("statuses/update: got status=%q, want %q", got, want)
209 }
210 if got, want := req.FormValue("media_ids"), "media-123"; got != want {
211 t.Errorf("statuses/update: got media_ids=%q, want %q", got, want)
212 }
213 mustWrite(w, `{"id_str": "tweet-123", "user": {"screen_name": "golang"}}`)
214 })
215 httpClient := &http.Client{Transport: localRoundTripper{mux}}
216
217 tweetURL, err := postTweet(httpClient, "tweet-text", []byte("image-png-bytes"))
218 if err != nil {
219 t.Fatal("postTweet:", err)
220 }
221 if got, want := tweetURL, "https://twitter.com/golang/status/tweet-123"; got != want {
222 t.Errorf("got tweetURL=%q, want %q", got, want)
223 }
224}
225
226// localRoundTripper is an http.RoundTripper that executes HTTP transactions
227// by using handler directly, instead of going over an HTTP connection.
228type localRoundTripper struct {
229 handler http.Handler
230}
231
232func (l localRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
233 w := httptest.NewRecorder()
234 l.handler.ServeHTTP(w, req)
235 return w.Result(), nil
236}
237
238func mustRead(r io.Reader) string {
239 b, err := io.ReadAll(r)
240 if err != nil {
241 panic(err)
242 }
243 return string(b)
244}
245
246func mustWrite(w io.Writer, s string) {
247 _, err := io.WriteString(w, s)
248 if err != nil {
249 panic(err)
250 }
251}