diff --git a/.github/workflows/test.yml b/.github/workflows/unit.yml similarity index 91% rename from .github/workflows/test.yml rename to .github/workflows/unit.yml index 709b2ef..d981d01 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/unit.yml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -name: 'Test' +name: 'unit' on: push: @@ -23,9 +23,10 @@ on: pull_request: branches: - 'main' + workflow_dispatch: concurrency: - group: '${{ github.head_ref || github.ref }}' + group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}' cancel-in-progress: true jobs: @@ -62,4 +63,4 @@ jobs: ${{ matrix.os }}-go- - name: 'Test' - run: make test-acc + run: 'make test-acc' diff --git a/actions.go b/actions.go index d52f9b3..6345845 100644 --- a/actions.go +++ b/actions.go @@ -44,9 +44,9 @@ const ( addPathCmd = "add-path" // used when issuing the regular command pathCmd = "path" // used when issuing the file command - setEnvCmd = "set-env" // used when issuing the regular command - envCmd = "env" // used when issuing the file command - envCmdMsgFmt = "%s<<%s\n%s\n%s" // ${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter} + setEnvCmd = "set-env" // used when issuing the regular command + envCmd = "env" // used when issuing the file command + envCmdMsgFmt = "%s<<%s" + EOF + "%s" + EOF + "%s" // ${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter} envCmdDelimiter = "_GitHubActionsFileCommandDelimeter_" addMatcherCmd = "add-matcher" @@ -56,8 +56,9 @@ const ( endGroupCmd = "endgroup" debugCmd = "debug" - errorCmd = "error" + noticeCmd = "notice" warningCmd = "warning" + errorCmd = "error" errFileCmdFmt = "unable to write command to the environment file: %s" ) @@ -103,7 +104,7 @@ type Action struct { // IssueCommand issues a new GitHub actions Command. func (c *Action) IssueCommand(cmd *Command) { - fmt.Fprintln(c.w, cmd.String()) + fmt.Fprint(c.w, cmd.String()+EOF) } // IssueFileCommand issues a new GitHub actions Command using environment files. @@ -122,8 +123,9 @@ func (c *Action) IssueFileCommand(cmd *Command) error { e = strings.ToUpper(e) e = "GITHUB_" + e - err := ioutil.WriteFile(c.getenv(e), []byte(cmd.Message+"\n"), os.ModeAppend) - if err != nil { + filepath := c.getenv(e) + msg := []byte(cmd.Message + EOF) + if err := ioutil.WriteFile(filepath, msg, os.ModeAppend); err != nil { return fmt.Errorf(errFileCmdFmt, err) } @@ -257,8 +259,8 @@ func (c *Action) SetOutput(k, v string) { }) } -// Debugf prints a debug-level message. The arguments follow the standard Printf -// arguments. +// Debugf prints a debug-level message. It follows the standard fmt.Printf +// arguments, appending an operating-system to the end of the message. func (c *Action) Debugf(msg string, args ...interface{}) { // ::debug :: c.IssueCommand(&Command{ @@ -268,8 +270,30 @@ func (c *Action) Debugf(msg string, args ...interface{}) { }) } -// Errorf prints a error-level message. The arguments follow the standard Printf -// arguments. +// Noticef prints a notice-level message. It follows the standard fmt.Printf +// arguments, appending an operating-system to the end of the message. +func (c *Action) Noticef(msg string, args ...interface{}) { + // ::notice :: + c.IssueCommand(&Command{ + Name: noticeCmd, + Message: fmt.Sprintf(msg, args...), + Properties: c.fields, + }) +} + +// Warningf prints a warning-level message. It follows the standard fmt.Printf +// arguments, appending an operating-system to the end of the message. +func (c *Action) Warningf(msg string, args ...interface{}) { + // ::warning :: + c.IssueCommand(&Command{ + Name: warningCmd, + Message: fmt.Sprintf(msg, args...), + Properties: c.fields, + }) +} + +// Errorf prints a error-level message. It follows the standard fmt.Printf +// arguments, appending an operating-system to the end of the message. func (c *Action) Errorf(msg string, args ...interface{}) { // ::error :: c.IssueCommand(&Command{ @@ -286,22 +310,11 @@ func (c *Action) Fatalf(msg string, args ...interface{}) { osExit(1) } -// Infof prints a info-level message. The arguments follow the standard Printf -// arguments. +// Infof prints message to stdout without any level annotations. It follows the +// standard fmt.Printf arguments, appending an operating-system to the end of +// the message. func (c *Action) Infof(msg string, args ...interface{}) { - // ::info :: - fmt.Fprintf(c.w, msg, args...) -} - -// Warningf prints a warning-level message. The arguments follow the standard -// Printf arguments. -func (c *Action) Warningf(msg string, args ...interface{}) { - // ::warning :: - c.IssueCommand(&Command{ - Name: warningCmd, - Message: fmt.Sprintf(msg, args...), - Properties: c.fields, - }) + fmt.Fprintf(c.w, msg+EOF, args...) } // WithFieldsSlice includes the provided fields in log output. "f" must be a diff --git a/actions_test.go b/actions_test.go index 7f2af21..956860c 100644 --- a/actions_test.go +++ b/actions_test.go @@ -44,7 +44,7 @@ func TestNew(t *testing.T) { Message: "bar", }) - if got, want := b.String(), "::foo::bar\n"; got != want { + if got, want := b.String(), "::foo::bar"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } @@ -64,7 +64,7 @@ func TestNewWithWriter(t *testing.T) { Message: "bar", }) - if got, want := b.String(), "::foo::bar\n"; got != want { + if got, want := b.String(), "::foo::bar"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -79,7 +79,7 @@ func TestAction_IssueCommand(t *testing.T) { Message: "bar", }) - if got, want := b.String(), "::foo::bar\n"; got != want { + if got, want := b.String(), "::foo::bar"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -118,7 +118,7 @@ func TestAction_IssueFileCommand(t *testing.T) { t.Errorf("unable to read temp env file: %s", err) } - if got, want := string(data), "bar\n"; got != want { + if got, want := string(data), "bar"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -130,7 +130,7 @@ func TestAction_AddMask(t *testing.T) { a := New(WithWriter(&b)) a.AddMask("foobar") - if got, want := b.String(), "::add-mask::foobar\n"; got != want { + if got, want := b.String(), "::add-mask::foobar"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -142,7 +142,7 @@ func TestAction_AddMatcher(t *testing.T) { a := New(WithWriter(&b)) a.AddMatcher("foobar.json") - if got, want := b.String(), "::add-matcher::foobar.json\n"; got != want { + if got, want := b.String(), "::add-matcher::foobar.json"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -154,7 +154,7 @@ func TestAction_RemoveMatcher(t *testing.T) { a := New(WithWriter(&b)) a.RemoveMatcher("foobar") - if got, want := b.String(), "::remove-matcher owner=foobar::\n"; got != want { + if got, want := b.String(), "::remove-matcher owner=foobar::"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -170,7 +170,7 @@ func TestAction_AddPath(t *testing.T) { a := New(WithWriter(&b), WithGetenv(fakeGetenvFunc)) a.AddPath("/custom/bin") - if got, want := b.String(), "::add-path::/custom/bin\n"; got != want { + if got, want := b.String(), "::add-path::/custom/bin"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } @@ -203,7 +203,7 @@ func TestAction_AddPath(t *testing.T) { t.Errorf("unable to read temp env file: %s", err) } - if got, want := string(data), "/custom/bin\n"; got != want { + if got, want := string(data), "/custom/bin"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -215,7 +215,7 @@ func TestAction_SaveState(t *testing.T) { a := New(WithWriter(&b)) a.SaveState("key", "value") - if got, want := b.String(), "::save-state name=key::value\n"; got != want { + if got, want := b.String(), "::save-state name=key::value"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -239,7 +239,7 @@ func TestAction_Group(t *testing.T) { a := New(WithWriter(&b)) a.Group("mygroup") - if got, want := b.String(), "::group::mygroup\n"; got != want { + if got, want := b.String(), "::group::mygroup"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -251,7 +251,7 @@ func TestAction_EndGroup(t *testing.T) { a := New(WithWriter(&b)) a.EndGroup() - if got, want := b.String(), "::endgroup::\n"; got != want { + if got, want := b.String(), "::endgroup::"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -265,8 +265,8 @@ func TestAction_SetEnv(t *testing.T) { checks := []struct { key, value, want string }{ - {"key", "value", "::set-env name=key::value\n"}, - {"key", "this is 100% a special\n\r value!", "::set-env name=key::this is 100%25 a special%0A%0D value!\n"}, + {"key", "value", "::set-env name=key::value" + EOF}, + {"key", "this is 100% a special\n\r value!", "::set-env name=key::this is 100%25 a special%0A%0D value!" + EOF}, } for _, check := range checks { @@ -302,7 +302,7 @@ func TestAction_SetEnv(t *testing.T) { t.Errorf("unable to read temp env file: %s", err) } - want := "key<<_GitHubActionsFileCommandDelimeter_\nvalue\n_GitHubActionsFileCommandDelimeter_\n" + want := "key<<_GitHubActionsFileCommandDelimeter_" + EOF + "value" + EOF + "_GitHubActionsFileCommandDelimeter_" + EOF if got := string(data); got != want { t.Errorf("expected %q to be %q", got, want) } @@ -315,7 +315,7 @@ func TestAction_SetOutput(t *testing.T) { a := New(WithWriter(&b)) a.SetOutput("key", "value") - if got, want := b.String(), "::set-output name=key::value\n"; got != want { + if got, want := b.String(), "::set-output name=key::value"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -327,7 +327,31 @@ func TestAction_Debugf(t *testing.T) { a := New(WithWriter(&b)) a.Debugf("fail: %s", "thing") - if got, want := b.String(), "::debug::fail: thing\n"; got != want { + if got, want := b.String(), "::debug::fail: thing"+EOF; got != want { + t.Errorf("expected %q to be %q", got, want) + } +} + +func TestAction_Noticef(t *testing.T) { + t.Parallel() + + var b bytes.Buffer + a := New(WithWriter(&b)) + a.Noticef("fail: %s", "thing") + + if got, want := b.String(), "::notice::fail: thing"+EOF; got != want { + t.Errorf("expected %q to be %q", got, want) + } +} + +func TestAction_Warningf(t *testing.T) { + t.Parallel() + + var b bytes.Buffer + a := New(WithWriter(&b)) + a.Warningf("fail: %s", "thing") + + if got, want := b.String(), "::warning::fail: thing"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -339,7 +363,7 @@ func TestAction_Errorf(t *testing.T) { a := New(WithWriter(&b)) a.Errorf("fail: %s", "thing") - if got, want := b.String(), "::error::fail: thing\n"; got != want { + if got, want := b.String(), "::error::fail: thing"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -355,7 +379,7 @@ func TestAction_Fatalf(t *testing.T) { a := New(WithWriter(&b)) a.Fatalf("fail: %s", "bring") - if got, want := b.String(), "::error::fail: bring\n"; got != want { + if got, want := b.String(), "::error::fail: bring"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } @@ -364,26 +388,14 @@ func TestAction_Fatalf(t *testing.T) { } } -func TestAction_Warningf(t *testing.T) { - t.Parallel() - - var b bytes.Buffer - a := New(WithWriter(&b)) - a.Warningf("fail: %s", "thing") - - if got, want := b.String(), "::warning::fail: thing\n"; got != want { - t.Errorf("expected %q to be %q", got, want) - } -} - func TestAction_Infof(t *testing.T) { t.Parallel() var b bytes.Buffer a := New(WithWriter(&b)) - a.Infof("info: %s\n", "thing") + a.Infof("info: %s", "thing") - if got, want := b.String(), "info: thing\n"; got != want { + if got, want := b.String(), "info: thing"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -396,7 +408,7 @@ func TestAction_WithFieldsSlice(t *testing.T) { a = a.WithFieldsSlice([]string{"line=100", "file=app.js"}) a.Debugf("fail: %s", "thing") - if got, want := b.String(), "::debug file=app.js,line=100::fail: thing\n"; got != want { + if got, want := b.String(), "::debug file=app.js,line=100::fail: thing"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -425,7 +437,7 @@ func TestAction_WithFieldsMap(t *testing.T) { a = a.WithFieldsMap(map[string]string{"line": "100", "file": "app.js"}) a.Debugf("fail: %s", "thing") - if got, want := b.String(), "::debug file=app.js,line=100::fail: thing\n"; got != want { + if got, want := b.String(), "::debug file=app.js,line=100::fail: thing"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } diff --git a/eof_crlf.go b/eof_crlf.go new file mode 100644 index 0000000..12d0d31 --- /dev/null +++ b/eof_crlf.go @@ -0,0 +1,20 @@ +// Copyright 2021 The Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build windows +// +build windows + +package githubactions + +const EOF = "\r\n" diff --git a/eof_lf.go b/eof_lf.go new file mode 100644 index 0000000..b2c3456 --- /dev/null +++ b/eof_lf.go @@ -0,0 +1,20 @@ +// Copyright 2021 The Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !windows +// +build !windows + +package githubactions + +const EOF = "\n" diff --git a/options_test.go b/options_test.go index 512f9bc..1c36376 100644 --- a/options_test.go +++ b/options_test.go @@ -32,7 +32,7 @@ func TestWithWriter(t *testing.T) { Message: "bar", }) - if got, want := b.String(), "::foo::bar\n"; got != want { + if got, want := b.String(), "::foo::bar"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } } @@ -51,7 +51,7 @@ func TestWithFields(t *testing.T) { Properties: a.fields, }) - if got, want := b.String(), "::foo baz=quux::bar\n"; got != want { + if got, want := b.String(), "::foo baz=quux::bar"+EOF; got != want { t.Errorf("expected %q to be %q", got, want) } }