Skip to content

Commit

Permalink
cmd/relnot: Release notes generator
Browse files Browse the repository at this point in the history
  • Loading branch information
codebien committed Sep 29, 2023
1 parent 6fa8abd commit 0ac611a
Show file tree
Hide file tree
Showing 8 changed files with 485 additions and 0 deletions.
54 changes: 54 additions & 0 deletions cmd/relnot/change.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

Check warning on line 1 in cmd/relnot/change.go

View workflow job for this annotation

GitHub Actions / lint

package-comments: should have a package comment (revive)

import (
"fmt"
)

var contributors = map[string]bool{
// core team
"mstoykov": true,
"codebien": true,
"olegbespalov": true,
"oleiade": true,

// browser team
"ankur22": true,
"inancgumus": true,
"ka3de": true,
}

type Change struct {

Check warning on line 20 in cmd/relnot/change.go

View workflow job for this annotation

GitHub Actions / lint

exported: exported type Change should have comment or be unexported (revive)
RawType string
Type PullType
Number int
Title string
Body string
Author string
}

func (c Change) Format() string {

Check warning on line 29 in cmd/relnot/change.go

View workflow job for this annotation

GitHub Actions / lint

exported: exported method Change.Format should have comment or be unexported (revive)
if c.isEpic() {
return c.formatEpic()
}
text := fmt.Sprintf("- [#%d](https://github.com/grafana/k6/pull/%d) %s", c.Number, c.Number, c.Title)
if c.isExternalContributor() {
text += fmt.Sprintf(" Thanks @%s for the contribution!.", c.Author)
}
return text
}

func (c Change) formatEpic() string {
text := fmt.Sprintf("### %s\n\n%s", c.Title, c.Body)
if c.isExternalContributor() {
text += fmt.Sprintf("\nThanks @%s for the contribution!.", c.Author)
}
return text + "\n"
}

func (c Change) isEpic() bool {
return c.Type == EpicFeature || c.Type == EpicBreaking
}

func (c Change) isExternalContributor() bool {
return contributors[c.Author]
}
18 changes: 18 additions & 0 deletions cmd/relnot/change_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package main

import (
"testing"
)

func TestChangeFormatTypeNoBody(t *testing.T) {
c := Change{
Type: Bug,
Number: 3231,
Title: "Fixes the tracing module sampling option to default to 1.0 when not set by the user.",
Body: "",
}
exp := "- [#3231](https://github.com/grafana/k6/pull/3231) Fixes the tracing module sampling option to default to 1.0 when not set by the user."
if s := c.Format(); s != exp {
t.Errorf("unexpected formatted change: got: %s", s)
}
}
234 changes: 234 additions & 0 deletions cmd/relnot/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package main

import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"os"
"os/exec"
"strings"
)

const (
// TODO: we may consider to input it as an argument
// e.g.
// $ relnot -m v0.47.0
milestone = "v0.47.0"

// TODO: we may consider to input it as an argument
// e.g.
// $ relnot -m v0.47.0 ./release\ notes/unreleased.md
unreleasedFilePath = "./../../release notes/unreleased.md"
)

func main() {
f, err := openUnreleased(unreleasedFilePath)
if err != nil {
log.Fatalf("open Unreleased file failed: %v", err)
}
defer f.Close()

Check failure on line 32 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `f.Close` is not checked (errcheck)

fmt.Println("Unreleased file descriptor opened")

pulls, err := fetchPullRequests()
if err != nil {
log.Fatalf("fetch pull requests failed: %v", err)
}

if len(pulls) < 0 {
fmt.Println("There aren't pull requests to process, the generation will be stopped.")
}

fmt.Printf("Pull requests fetched: %d\n\n", len(pulls))

changesByType := make(map[PullType][]Change)
changesContents := make(map[PullType]string)

log.Println("Parsing changes from Pull request:")
for _, pull := range pulls {
fmt.Printf("#%d ", pull.Number)

change, err := parseChange(pull)
if err != nil {
log.Fatalf("parse change (#%d) failed: %v", change.Number, err)
return
}

changesByType[change.Type] = append(changesByType[change.Type], change)
if change.Type == Undefined {
continue
}

text := change.Format()
changesContents[change.Type] += text + "\n"
}

fmt.Println("\n\nMatching stage report:")
for typ, changes := range changesByType {
if typ == Undefined {
fmt.Printf("Type: %s, Count: %d, Pulls: %v\n", typ, len(changes), mapChangesNumbers(changes))
continue
}
fmt.Printf("Type: %s, Count: %d\n", typ, len(changes))
}

ftemp, err := os.CreateTemp("", "")

Check failure on line 78 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

use of `os.CreateTemp` forbidden because "Using anything except Signal and SyscallError from the os package is forbidden" (forbidigo)
if err != nil {
log.Fatalf("open the temporary Unreleased joiner failed: %v", err)
}
defer os.Remove(ftemp.Name())

Check failure on line 82 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `os.Remove` is not checked (errcheck)
defer ftemp.Close()

Check failure on line 83 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `ftemp.Close` is not checked (errcheck)

joiner := bufio.NewWriter(ftemp)
scanner := bufio.NewScanner(f)

for scanner.Scan() {
text := scanner.Text()
if len(text) < 1 || text[0] != '<' {
joiner.Write([]byte(text + "\n"))

Check failure on line 91 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `joiner.Write` is not checked (errcheck)
continue
}

typeToAdd, err := typeFromPlaceholder(text)
if err != nil {
log.Fatalf("expected to parse a placeholder but it failed on the line: %s", err)
return
}

changesToAdd, ok := changesContents[typeToAdd]
if !ok {
joiner.Write([]byte(placeholder(typeToAdd) + "\n"))

Check failure on line 103 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `joiner.Write` is not checked (errcheck)
continue
}
joiner.Write([]byte(changesToAdd + placeholder(typeToAdd) + "\n"))

Check failure on line 106 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `joiner.Write` is not checked (errcheck)
}

if err := scanner.Err(); err != nil {
log.Fatal(err)
}

joiner.Flush()

Check failure on line 113 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `joiner.Flush` is not checked (errcheck)
ftemp.Close()

Check failure on line 114 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `ftemp.Close` is not checked (errcheck)

if err := os.Rename(ftemp.Name(), unreleasedFilePath); err != nil {

Check failure on line 116 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

use of `os.Rename` forbidden because "Using anything except Signal and SyscallError from the os package is forbidden" (forbidigo)
log.Fatalf("Moving the new unreleased version failed: %s", err)
return
}

fmt.Println("\nRelease notes generation completed.")
}

func placeholder(typ PullType) string {
return fmt.Sprintf("<%s>", typ.String())
}

func typeFromPlaceholder(text string) (PullType, error) {
text = strings.TrimPrefix(text, "<")
text = strings.TrimSuffix(text, ">")
return PullTypeString(text)
}

type Pull struct {

Check warning on line 134 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

exported: exported type Pull should have comment or be unexported (revive)
Number int `json:"number"`
Body string `json:"body"`
Author struct {
Username string `json:"login"`
} `json:"author"`
}

func parseChange(p Pull) (Change, error) {
c := Change{Number: p.Number}
_, changeBody, found := strings.Cut(p.Body, "### Changelog\r\n\r\n")
if !found {
return c, nil
}

bodyParts := strings.SplitN(changeBody, "\r\n\r\n", 2)
firstLine := strings.SplitN(bodyParts[0], ":", 2)

changeType, err := PullTypeString(strings.ToLower(firstLine[0]))
if err != nil {
return c, fmt.Errorf("pull request's type parser: %w", err)
}

c.Number = p.Number
c.Type = changeType
c.Title = strings.Trim(firstLine[1], " ")

if len(bodyParts) > 1 {
c.Body = bodyParts[1]
}

return c, nil
}

func fetchPullRequests() ([]Pull, error) {
// It requires pager to be set to `cat` => $ gh config set pager cat
fetchCmd := []string{
"pr", "list", "--repo", "grafana/k6", "-s", "merged",
"--search", fmt.Sprintf("milestone:%s sort:created-desc", milestone),
"--json", "number,body,author",
"--limit", "1000"}

cmd := exec.Command("gh", fetchCmd...)

stdout, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("invoke CLI command: %w", err)
}

var pulls []Pull
err = json.Unmarshal(stdout, &pulls)
if err != nil {
return nil, fmt.Errorf("JSON unmarhsal: %w", err)
}

return pulls, err
}

func mapChangesNumbers(changes []Change) []int {
nums := make([]int, 0, len(changes))
for _, p := range changes {
nums = append(nums, p.Number)
}
return nums
}

func openUnreleased(path string) (io.ReadCloser, error) {

Check warning on line 200 in cmd/relnot/main.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 'path' seems to be unused, consider removing or renaming it as _ (revive)
f, err := os.Open(unreleasedFilePath)
if os.IsNotExist(err) {
return io.NopCloser(bytes.NewBufferString(template)), nil
}
if err != nil {
return nil, err
}
return f, err
}

var template = `## Breaking changes
<epic-breaking>
<breaking>
## New features
<epic-feature>
<feature>
### UX improvements and enhancements
<ux>
## Bug fixes
<bug>
## Maintenance and internal improvements
<internal>
`
40 changes: 40 additions & 0 deletions cmd/relnot/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"encoding/json"
"io"
"os"
"testing"
)

func TestParseChange(t *testing.T) {
testdata, err := os.Open("./testdata/pulls.json")
if err != nil {
t.Fatal(err)
}

input, err := io.ReadAll(testdata)
if err != nil {
t.Fatal(err)
}

var pulls []Pull
err = json.Unmarshal(input, &pulls)
if err != nil {
t.Fatal(err)
}

var changes []Change
for _, pull := range pulls {
change, err := parseChange(pull)
if err != nil {
t.Fatal(err)
}
changes = append(changes, change)
}

if len(changes) != 10 {
t.Errorf("unexpected identified changes")

}
}
42 changes: 42 additions & 0 deletions cmd/relnot/testdata/pulls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[
{
"body": "## What?\r\n\r\nThis upgrades the [xk6-browser](https://github.com/grafana/xk6-browser) extension to [v1.1.0](https://github.com/grafana/xk6-browser/releases/tag/v1.1.0) in k6.\r\n\r\n## Why?\r\n\r\nUpgrade the extension in preparation for an upcoming release.\r\n\r\n## Checklist\r\n\r\n<!-- \r\nIf you haven't read the contributing guidelines https://github.com/grafana/k6/blob/master/CONTRIBUTING.md \r\nand code of conduct https://github.com/grafana/k6/blob/master/CODE_OF_CONDUCT.md yet, please do so\r\n-->\r\n\r\n- [X] I have performed a self-review of my code.\r\n- [ ] I have added tests for my changes. (NA)\r\n- [X] I have run linter locally (`make ci-like-lint`) and all checks pass.\r\n- [ ] I have run tests locally (`make tests`) and all tests pass.\r\n- [ ] I have commented on my code, particularly in hard-to-understand areas. (NA)\r\n<!-- - [ ] Any other relevant item -->\r\n\r\n## Related PR(s)/Issue(s)\r\n\r\n<!-- - <https://github.com/grafana/...> -->\r\n\r\n<!-- Does it close an issue? -->\r\n\r\n<!-- Closes #ISSUE-ID -->\r\n\r\n<!-- Thanks for your contribution! 🙏🏼 -->\r\n",
"number": 3355
},
{
"body": "## What?\r\n\r\nIt bumps the Prometheus remote write to the latest version\r\n\r\n---\r\n\r\n### Changelog\r\n\r\ninternal: Updated the Prometheus remote write output version. Check the specific [release notes](https://github.com/grafana/xk6-output-prometheus-remote/releases/tag/v0.3.0).\r\n\r\nThis is just an example body. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s.\r\n",
"number": 3351
},
{
"body": "Updates #2982\r\n\r\n### Changelog\r\n\r\nbreaking: Deprecate statsd output\r\n\r\nStatsd output has not been maintained very actively by the k6 core team and due to it not being very standard that isn't very likely to change. As such it has been decided that it will be moved to an extension and given to an outside maintainer that can better work on it.\r\n\r\nThe new extension can be found at https://github.com/LeonAdato/xk6-output-statsd\r\n",
"number": 3347
},
{
"body": "Previous http.batch code did call `runtime.NewArrayBuffer` concurrently.\r\n\r\nWhich never has been guaranteed to be safe, but with the latest goja changes to having prototypes created on demand it races.\r\n\r\nThe fix *might* have some performance implications, but they are likely to be very small as `NewArrayBuffer` mostly wraps stuff so ... hopefully not a big deal.\r\n\r\n### Changelog\r\n\r\nbug: fix data race when using `http.batch` and binary response\r\n\r\nThere has been concurrent access of the goja runtime/js vm when `http.batch` is used with binary response for a long time. Up until recently that was not so relevant, but with latest goja changes an actual data race was detected and is now fixed.\r\n\r\n\r\n",
"number": 3346
},
{
"body": "## What?\r\n\r\nThis PR brings support of google wrappers to the k6.\r\n\r\n## Why?\r\n\r\nThe reported in #3232 k6 previously did not correctly marshal the google wrappers messages.\r\n\r\n## Checklist\r\n\r\n<!-- \r\nIf you haven't read the contributing guidelines https://github.com/grafana/k6/blob/master/CONTRIBUTING.md \r\nand code of conduct https://github.com/grafana/k6/blob/master/CODE_OF_CONDUCT.md yet, please do so\r\n-->\r\n\r\n- [x] I have performed a self-review of my code.\r\n- [x] I have added tests for my changes.\r\n- [x] I have run linter locally (`make ci-like-lint`) and all checks pass.\r\n- [x] I have run tests locally (`make tests`) and all tests pass.\r\n- [ ] I have commented on my code, particularly in hard-to-understand areas.\r\n<!-- - [ ] Any other relevant item -->\r\n\r\n## Related PR(s)/Issue(s)\r\n\r\nhttps://github.com/grafana/xk6-grpc/pull/49\r\n\r\n<!-- Does it close an issue? -->\r\n\r\nResolves #3232\r\n\r\nCloses #3238 \r\n\r\n<!-- Thanks for your contribution! 🙏🏼 -->\r\n\r\n### Changelog\r\n\r\nbug: adds (fixes) the support of google protobuf wrappers\r\n\r\nadds (fixes) the support of google protobuf wrappers\r\n",
"number": 3344
},
{
"body": "## What?\r\n\r\nThis PR contains several parts:\r\n* First is refactoring of calling and connection params to make code more re-usable\r\n* Second is the significant (but is mostly moving & cleaning) refactoring of the gRPC client's test\r\n* last but not least is an introduction to the `reflectMetadata` \r\n\r\n## Why?\r\n\r\nThe refactoring was done to make the following changes tinier and pay some technical dept of removing copy-pasting.\r\n\r\nThe `reflectMetadata` was requested in #3241 \r\n\r\n## Checklist\r\n\r\n<!-- \r\nIf you haven't read the contributing guidelines https://github.com/grafana/k6/blob/master/CONTRIBUTING.md \r\nand code of conduct https://github.com/grafana/k6/blob/master/CODE_OF_CONDUCT.md yet, please do so\r\n-->\r\n\r\n- [x] I have performed a self-review of my code.\r\n- [x] I have added tests for my changes.\r\n- [x] I have run linter locally (`make ci-like-lint`) and all checks pass.\r\n- [x] I have run tests locally (`make tests`) and all tests pass.\r\n- [x] I have commented on my code, particularly in hard-to-understand areas.\r\n<!-- - [ ] Any other relevant item -->\r\n\r\n## Related PR(s)/Issue(s)\r\n\r\nhttps://github.com/grafana/xk6-grpc/pull/51\r\n\r\n<!-- Does it close an issue? -->\r\n\r\nCloses: #3241\r\n\r\n<!-- Thanks for your contribution! 🙏🏼 -->\r\n\r\n### Changelog\r\n\r\nepic-feature: a new gRPC connection's parameter `reflectMetadata`\r\n\r\nIn some workflows, the reflection call should also include some metadata. This PR adds [a new connection parameter `reflectMetadata`](https://k6.io/docs/javascript-api/k6-net-grpc/client/client-connect/#connectparams) that allows to specify the metadata to be sent with the reflection call.",
"number": 3343
},
{
"body": "## What?\r\n\r\nThis PR updates xk6-grpc dependency to the latest available version.\r\n\r\nThis change brings the new `reflectMetadata` to the connection parameters.\r\n\r\n## Why?\r\n\r\nThis is likely the version of the `k6/experimental/grpc` module that will be released in the following k6 v0.47.\r\n\r\n## Checklist\r\n\r\n<!-- \r\nIf you haven't read the contributing guidelines https://github.com/grafana/k6/blob/master/CONTRIBUTING.md \r\nand code of conduct https://github.com/grafana/k6/blob/master/CODE_OF_CONDUCT.md yet, please do so\r\n-->\r\n\r\n- [x] I have performed a self-review of my code.\r\n- [ ] I have added tests for my changes.\r\n- [ ] I have run linter locally (`make ci-like-lint`) and all checks pass.\r\n- [ ] I have run tests locally (`make tests`) and all tests pass.\r\n- [ ] I have commented on my code, particularly in hard-to-understand areas.\r\n<!-- - [ ] Any other relevant item -->\r\n\r\n## Related PR(s)/Issue(s)\r\n\r\nhttps://github.com/grafana/xk6-grpc/pull/51\r\n\r\n<!-- Does it close an issue? -->\r\n\r\n<!-- Closes #ISSUE-ID -->\r\n\r\n<!-- Thanks for your contribution! 🙏🏼 -->\r\n\r\n### Changelog\r\n\r\ninternal: Updates xk6-grpc to the latest version.\r\n\r\nThis change brings all the latest fixes and improvements to the experimental gRPC module.\r\n",
"number": 3342
},
{
"body": "### Changelog\r\n\r\ninternal: update goja with some fixes for source indexes",
"number": 3341
},
{
"body": "## What?\r\n\r\nSets the `no-sandbox` chrome argument by default through the `K6_BROWSER_ARGS` ENV variable in the Dockerfile definition for the browser enabled docker image.\r\n\r\n## Why?\r\n\r\nIn order to allow chrome to execute in the browser enabled image, either `SYS_ADMIN` Docker capability or `no-sandbox` browser argument had to be set. This sets the `no-sandbox` option in the Dockerfile definition itself so this action is no longer necessary to run the Docker container. This is arguably also the better option, as we are running the chrome browser inside a container, versus using the Docker capability.\r\n\r\n## Checklist\r\n\r\n- [X] I have performed a self-review of my code.\r\n- [ ] I have added tests for my changes.\r\n- [X] I have run linter locally (`make ci-like-lint`) and all checks pass.\r\n- [X] I have run tests locally (`make tests`) and all tests pass.\r\n- [X] I have commented on my code, particularly in hard-to-understand areas.\r\n",
"number": 3340
},
{
"body": "This updates makes the runtime initialization faster and *lighter*.\r\n\r\nFor empty and fairly small scripts the observed initialization improvement by some fast \"benchmarking\" for k6 this means:\r\n1. around 3x faster initialization of VUs\r\n2. around 4x less memory usage.\r\n\r\nThis likely will be nearly unnoticeable as this is only reasonably easy to see with 100k VUs for the initialization speed where it goes from around 10s to a little over 3s.\r\n\r\nAdding more code increases both linearly, so bigger scripts will have some speed up, but it won't be 3x.\r\n\r\nSame seems to be for memory usages as well.\r\n\r\nBoth of those likely get way worse if more and more of the JavaScript \"standard library\" is used as in that case all of those now templated properties will be used.\r\n\r\n### Changelog\r\n\r\ninternal: update goja with some runtime initialization speed-ups\r\n\r\nThe speed-ups are around 3x and there is 4x memory reduction for *empty* scripts. Anything more complex adds and using JS types or importing libraries decrease this as it just adds to the actual initialization in ways this optimization does not help. \r\n",
"number": 3339
}
]
Loading

0 comments on commit 0ac611a

Please sign in to comment.