Skip to content

Commit

Permalink
Print errors to stderr
Browse files Browse the repository at this point in the history
  • Loading branch information
ypjama committed Oct 18, 2023
1 parent be5cdc8 commit fd93f77
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 69 deletions.
3 changes: 3 additions & 0 deletions changes/errors.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
changed:
- Errors are outputted to stderr instead of stdout
3 changes: 2 additions & 1 deletion internal/pkg/conflictless/check.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package conflictless

func check(cfg *Config) {
// Check checks the validity of the change files.
func Check(cfg *Config) {
combined, err := scanDir(*cfg.Flags.Directory)
if err != nil {
PrintErrorAndExit(err.Error(), func() {})
Expand Down
69 changes: 69 additions & 0 deletions internal/pkg/conflictless/check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package conflictless_test

import (
"errors"
"os"
"os/exec"
"testing"

"github.com/stretchr/testify/assert"
"github.com/ypjama/conflictless-keepachangelog/internal/pkg/conflictless"
)

//nolint:paralleltest // this test is not parallel because it modifies os.Stdout.
func TestCheck(t *testing.T) {
cfg := new(conflictless.Config)

changesDir, err := os.MkdirTemp(os.TempDir(), "changes")
assert.NoError(t, err)

defer os.RemoveAll(changesDir)

changesFile := createFile(t, changesDir, "test-check.json")
defer os.Remove(changesFile.Name())

stdOutFile := createTempFile(t, os.TempDir(), "stdout-test-check")
defer os.Remove(stdOutFile.Name())

writeDataToFile(t, []byte(`{"fixed":["foo"]}`), changesFile)

cfg.Flags.Directory = &changesDir

os.Stdout = stdOutFile

conflictless.Check(cfg)

data, err := os.ReadFile(stdOutFile.Name())

assert.NoError(t, err)
assert.Equal(t, "Change files are valid!\n", string(data))
}

func TestCheckWhenDirectoryDoesNotExist(t *testing.T) {
t.Parallel()

if os.Getenv("TEST_CHECK_WHEN_ERROR") == "1" {
cfg := new(conflictless.Config)

nonExistentDir := os.TempDir() + "/foo/bar"
cfg.Flags.Directory = &nonExistentDir

conflictless.Check(cfg)

return
}

//nolint:gosec // this is a test package so G204 doesn't really matter here.
cmd := exec.Command(os.Args[0], "-test.run=^TestCheckWhenDirectoryDoesNotExist$")

cmd.Env = append(os.Environ(), "TEST_CHECK_WHEN_ERROR=1")
err := cmd.Run()

exitErr := new(*exec.ExitError)
errors.As(err, exitErr)

expectedCode := 2
exitCode := (*exitErr).ExitCode()

assert.Equal(t, expectedCode, exitCode, "process exited with %d, want exit status %d", expectedCode, exitCode)
}
10 changes: 5 additions & 5 deletions internal/pkg/conflictless/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ func CLI() {

switch cfg.Flags.Command {
case commandCheck:
check(&cfg)
Check(&cfg)
case commandGen:
Generate(&cfg)
case commandHelp:
help()
default:
PrintErrorAndExit(fmt.Sprintf("invalid command: '%s'", cfg.Flags.Command), usage)
PrintErrorAndExit(fmt.Sprintf("invalid command: '%s'", cfg.Flags.Command), usageOnError)
}
}

Expand All @@ -62,17 +62,17 @@ func parseCLIFlags(cfg *Config) {
switch cfg.Flags.Command {
case commandHelp:
cmd = flag.NewFlagSet(commandHelp, flag.ExitOnError)
cmd.Usage = usage
cmd.Usage = usageOnError
case commandGen:
cmd = flag.NewFlagSet(commandGen, flag.ExitOnError)
cmd.Usage = usageGenerate
cmd.Usage = usageGenerateOnError

defineBumpFlags(cfg, cmd)
defineDirFlags(cfg, cmd)
defineSkipFlags(cfg, cmd)
case commandCheck:
cmd = flag.NewFlagSet(commandCheck, flag.ExitOnError)
cmd.Usage = usageCheck
cmd.Usage = usageCheckOnError

defineDirFlags(cfg, cmd)
}
Expand Down
42 changes: 42 additions & 0 deletions internal/pkg/conflictless/conflictless_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package conflictless_test

import (
"os"
"testing"
)

const (
mkdirFileMode = 0o755
writeFileMode = 0o644
)

func writeDataToFile(t *testing.T, data []byte, file *os.File) {
t.Helper()

err := os.WriteFile(file.Name(), data, writeFileMode)
if err != nil {
t.Fatal(err)
}
}

func createFile(t *testing.T, dir, name string) *os.File {
t.Helper()

file, err := os.Create(dir + "/" + name)
if err != nil {
t.Fatal(err)
}

return file
}

func createTempFile(t *testing.T, dir, pattern string) *os.File {
t.Helper()

file, err := os.CreateTemp(dir, pattern)
if err != nil {
t.Fatal(err)
}

return file
}
2 changes: 1 addition & 1 deletion internal/pkg/conflictless/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
func Generate(cfg *Config) {
err := cfg.SetBumpFromFlags()
if err != nil {
PrintErrorAndExit(err.Error(), usageGenerate)
PrintErrorAndExit(err.Error(), usageGenerateOnError)
}

cfg.Changelog, err = ReadChangelog(cfg)
Expand Down
42 changes: 3 additions & 39 deletions internal/pkg/conflictless/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,6 @@ import (
"github.com/ypjama/conflictless-keepachangelog/internal/pkg/conflictless"
)

const (
mkdirFileMode = 0o755
writeFileMode = 0o644
)

func writeDataToFile(t *testing.T, data []byte, file *os.File) {
t.Helper()

err := os.WriteFile(file.Name(), data, writeFileMode)
if err != nil {
t.Fatal(err)
}
}

func createFile(t *testing.T, dir, name string) *os.File {
t.Helper()

file, err := os.Create(dir + "/" + name)
if err != nil {
t.Fatal(err)
}

return file
}

func createTempFile(t *testing.T, dir, pattern string) *os.File {
t.Helper()

file, err := os.CreateTemp(dir, pattern)
if err != nil {
t.Fatal(err)
}

return file
}

func TestGenerate(t *testing.T) {
t.Parallel()

Expand All @@ -54,13 +18,13 @@ func TestGenerate(t *testing.T) {

os.TempDir()

changesFile := createFile(t, changesDir, "1-foo.json")
changesFile := createFile(t, changesDir, "test-generate.json")
defer os.Remove(changesFile.Name())

changelogFile := createTempFile(t, os.TempDir(), "CHANGELOG.md")
changelogFile := createTempFile(t, os.TempDir(), "test-generate-CHANGELOG.md")
defer os.Remove(changelogFile.Name())

gitConfigFile := createTempFile(t, os.TempDir(), ".gitconfig")
gitConfigFile := createTempFile(t, os.TempDir(), "test-generate.gitconfig")
defer os.Remove(gitConfigFile.Name())

writeDataToFile(t, []byte(`{"fixed":["foo"]}`), changesFile)
Expand Down
5 changes: 2 additions & 3 deletions internal/pkg/conflictless/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
// PrintUsageAndExit prints the usage and exits.
func PrintUsageAndExit(cfg *Config) {
if cfg.Flags.Command == "" {
PrintErrorAndExit("", usage)
PrintErrorAndExit("", usageOnError)
}

switch cfg.Flags.Command {
Expand All @@ -26,8 +26,7 @@ func PrintUsageAndExit(cfg *Config) {
// PrintErrorAndExit prints an error message and exits.
func PrintErrorAndExit(msg string, usageFunc func()) {
if msg != "" {
//nolint:forbidigo
fmt.Printf("Error: %s\n\n", msg)
fmt.Fprintf(os.Stderr, "Error: %s\n\n", msg)
}

usageFunc()
Expand Down
8 changes: 3 additions & 5 deletions internal/pkg/conflictless/print_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package conflictless_test

import (
"errors"
"net/url"
"os"
"os/exec"
"testing"
Expand Down Expand Up @@ -79,9 +80,8 @@ func TestPrintUsageAndExit(t *testing.T) {
}
}

//nolint:paralleltest // this test is not parallel because it modifies os.Stdout.
func TestPrintCheckSuccess(t *testing.T) {
t.Parallel()

for _, testCase := range []struct {
description string
noContent bool
Expand All @@ -94,9 +94,7 @@ func TestPrintCheckSuccess(t *testing.T) {
testCase := testCase

t.Run("", func(t *testing.T) {
t.Parallel()

file := createTempFile(t, os.TempDir(), "test-stdout")
file := createTempFile(t, os.TempDir(), "stdout-"+url.QueryEscape(testCase.description))
defer os.Remove(file.Name())

os.Stdout = file
Expand Down
60 changes: 45 additions & 15 deletions internal/pkg/conflictless/usage.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package conflictless

import "fmt"
import (
"fmt"
"os"
)

const (
flagIndentation = "\t"
Expand All @@ -19,9 +22,8 @@ const (
"Skip version links in changelog file (default: false)"
)

func usage() {
//nolint:forbidigo
fmt.Print(`Usage: conflictless <command> [flags]
func usageText() string {
return `Usage: conflictless <command> [flags]
The commands are:
Expand All @@ -31,27 +33,55 @@ The commands are:
Use "conflictless help <topic>" for more information about that topic.
`)
`
}

func usageCheck() {
//nolint:forbidigo
fmt.Printf(`Usage: conflictless check [flags]
func usageTextForGenerate() string {
return fmt.Sprintf(`Usage: conflictless generate [flags]
The flags are:
%s
`, flagDescriptionDir)
%s
%s
`,
flagDescriptionBump,
flagDescriptionDir,
flagDescriptionSkipVersionLinks,
)
}

func usageGenerate() {
//nolint:forbidigo
fmt.Printf(`Usage: conflictless generate [flags]
func usageTextForCheck() string {
return fmt.Sprintf(`Usage: conflictless check [flags]
The flags are:
%s
%s
%s
`, flagDescriptionBump, flagDescriptionDir, flagDescriptionSkipVersionLinks)
`,
flagDescriptionDir,
)
}

func usage() {
fmt.Fprint(os.Stdout, usageText())
}

func usageOnError() {
fmt.Fprint(os.Stderr, usageText())
}

func usageCheck() {
fmt.Fprint(os.Stdout, usageTextForCheck())
}

func usageCheckOnError() {
fmt.Fprint(os.Stderr, usageTextForCheck())
}

func usageGenerate() {
fmt.Fprint(os.Stdout, usageTextForGenerate())
}

func usageGenerateOnError() {
fmt.Fprint(os.Stderr, usageTextForGenerate())
}

0 comments on commit fd93f77

Please sign in to comment.