Skip to content

Commit 989670e

Browse files
authored
Merge pull request #32 from AlphaOne1/redaction
Improve error handling, add nil writer check, and enhance test coverage.
2 parents 98d8da4 + 918a302 commit 989670e

4 files changed

Lines changed: 108 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
Release 0.1.2
2+
=============
3+
4+
- introduced ErrWriterNil to catch nil writers
5+
- better parameter validation
6+
- better error messages
7+
- improved tests with configurable broken IO
8+
- added parallel annotation
9+
110
Release 0.1.1
211
=============
312

geany.go

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import (
1414
"text/template"
1515
)
1616

17-
// geanyData contains the data that geany provies on itself.
17+
// ErrWriterNil indicates that the provided io.Writer is nil and cannot be used.
18+
var ErrWriterNil = errors.New("writer is nil")
19+
20+
// geanyData contains the data that geany provides on itself.
1821
type geanyData struct {
1922
VcsRevision string
2023
VcsTime string
@@ -24,12 +27,15 @@ type geanyData struct {
2427

2528
// logoData is the structure given as data to the templating engine.
2629
// It contains the geany provided build information and the user provided data.
30+
// The user is not required to pass data, in that case nil should be passed. In any case,
31+
// the data accessed in the logo template and provided data must match.
2732
type logoData struct {
28-
Geany geanyData
29-
Values any
33+
Geany geanyData // build information
34+
Values any // user provided data
3035
}
3136

32-
// getBuildInfo is a global variable to mock it easily in tests.
37+
// getBuildInfo is a global variable to mock it easily in tests. Take into account that
38+
// this variable can produce race conditions in parallel testing.
3339
var getBuildInfo = debug.ReadBuildInfo //nolint:gochecknoglobals
3440

3541
// prepareLogoData collects the build information and the user-provided data
@@ -45,7 +51,7 @@ func prepareLogoData(values any) logoData {
4551
Values: values,
4652
}
4753

48-
if buildInfo, ok := getBuildInfo(); ok {
54+
if buildInfo, ok := getBuildInfo(); ok && buildInfo != nil {
4955
result.Geany.GoVersion = buildInfo.GoVersion
5056

5157
for _, s := range buildInfo.Settings {
@@ -68,25 +74,29 @@ func prepareLogoData(values any) logoData {
6874
// PrintSimpleWriter outputs just the name of the program, the build information
6975
// and, in case, the user given data to a user provided io.Writer.
7076
func PrintSimpleWriter(writer io.Writer, values any) error {
77+
if writer == nil {
78+
return ErrWriterNil
79+
}
80+
7181
revData := prepareLogoData(values)
7282

7383
// normally we have the program's name given as the first argument
74-
if len(os.Args) > 0 {
75-
fmt.Printf("%s\n", os.Args[0])
84+
if len(os.Args) > 0 && os.Args[0] != "" {
85+
if _, err := fmt.Fprintf(writer, "%s\n", os.Args[0]); err != nil {
86+
return fmt.Errorf("could not write program name: %w", err)
87+
}
7688
}
7789

7890
encoder := json.NewEncoder(writer)
7991
encoder.SetIndent("", " ")
8092

8193
// we suppress the linter here, as we cannot guarantee for users data.
82-
err := encoder.Encode(revData) //nolint:musttag
83-
84-
if err == nil {
85-
_, err = fmt.Fprintln(writer)
94+
if err := encoder.Encode(revData); err != nil { //nolint:musttag
95+
return fmt.Errorf("could not encode user data: %w", err)
8696
}
8797

88-
if err != nil {
89-
return fmt.Errorf("could not write: %w", err)
98+
if _, err := fmt.Fprintln(writer); err != nil {
99+
return fmt.Errorf("could not write final newline: %w", err)
90100
}
91101

92102
return nil
@@ -105,23 +115,32 @@ func PrintSimple(values any) error {
105115
// - VcsModified
106116
// - GoVersion
107117
//
108-
// these can be referenced in the template, e.g., using {{ .VcsRevision }}.
118+
// these can be referenced in the template, e.g., using `{{ .Geany.VcsRevision }}`.
109119
// An additional custom value can be accessed via the Values field. Its type must match the way
110-
// that it is accessed in the logo.
120+
// that it is accessed in the logo and is accessed using e.g. `{{ .Values.Foo }}`.
121+
//
122+
// The template is parsed and executed. In case of an error, the program's name and user given
123+
// data are printed as JSON as fallback. The original error and, in case, the error of the fallback
124+
// are returned.
111125
func PrintLogoWriter(writer io.Writer, tmpl string, values any) error {
126+
if writer == nil {
127+
return ErrWriterNil
128+
}
129+
112130
revData := prepareLogoData(values)
113131

114132
logo := template.New("logo")
115-
template.Must(logo.Parse(tmpl))
133+
134+
if _, err := logo.Parse(tmpl); err != nil {
135+
return fmt.Errorf("could not parse template: %w", err)
136+
}
116137

117138
if err := logo.Execute(writer, revData); err != nil {
118139
return errors.Join(err, PrintSimpleWriter(writer, values))
119140
}
120141

121-
_, err := fmt.Fprintln(writer)
122-
123-
if err != nil {
124-
return fmt.Errorf("could not write: %w", err)
142+
if _, err := fmt.Fprintln(writer); err != nil {
143+
return fmt.Errorf("could not write final newline: %w", err)
125144
}
126145

127146
return nil

geany_internal_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ func Must[T any](t T, err error) T {
2020
}
2121

2222
func TestPrepareLogoDataNormal(t *testing.T) {
23+
t.Parallel()
24+
2325
logoData := prepareLogoData(nil)
2426

2527
buildInfo, ok := debug.ReadBuildInfo()
@@ -34,6 +36,8 @@ func TestPrepareLogoDataNormal(t *testing.T) {
3436
}
3537

3638
func TestPrepareLogoDataMocked(t *testing.T) {
39+
t.Parallel()
40+
3741
old := getBuildInfo
3842
getBuildInfo = func() (*debug.BuildInfo, bool) {
3943
result := debug.BuildInfo{

geany_test.go

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ func (b *BrokenNIO) Write(in []byte) (n int, err error) {
104104
}
105105

106106
func TestBrokenLogoWriter(t *testing.T) {
107+
t.Parallel()
108+
107109
target := BrokenNIO{}
108110

109111
err := geany.PrintLogoWriter(
@@ -112,10 +114,15 @@ func TestBrokenLogoWriter(t *testing.T) {
112114
nil)
113115

114116
require.Error(t, err, "simple writer printing error")
115-
require.Equal(t, "broken writer\ncould not write: broken writer", err.Error(), "not two broken writer errors")
117+
require.Equal(t,
118+
"broken writer\ncould not write program name: broken writer",
119+
err.Error(),
120+
"not two broken writer errors")
116121
}
117122

118123
func TestBrokenLogoWriterFallback(t *testing.T) {
124+
t.Parallel()
125+
119126
target := BrokenNIO{To: 1}
120127

121128
err := geany.PrintLogoWriter(
@@ -128,6 +135,8 @@ func TestBrokenLogoWriterFallback(t *testing.T) {
128135
}
129136

130137
func TestBrokenLogoWriterAtEnd(t *testing.T) {
138+
t.Parallel()
139+
131140
target := BrokenNIO{From: 2}
132141

133142
err := geany.PrintLogoWriter(
@@ -136,10 +145,12 @@ func TestBrokenLogoWriterAtEnd(t *testing.T) {
136145
nil)
137146

138147
require.Error(t, err, "simple writer printing error")
139-
assert.Equal(t, "could not write: broken writer", err.Error(), "just one broken writer error")
148+
assert.Equal(t, "could not write final newline: broken writer", err.Error(), "just one broken writer error")
140149
}
141150

142151
func TestBrokenSimpleWriter(t *testing.T) {
152+
t.Parallel()
153+
143154
target := BrokenNIO{}
144155

145156
require.Error(t,
@@ -149,10 +160,48 @@ func TestBrokenSimpleWriter(t *testing.T) {
149160
"simple writer no printing error")
150161
}
151162

163+
func TestBrokenSimpleWriterUserData(t *testing.T) {
164+
t.Parallel()
165+
166+
target := BrokenNIO{From: 1}
167+
168+
require.Error(t,
169+
geany.PrintSimpleWriter(
170+
&target,
171+
nil),
172+
"simple writer no printing error")
173+
}
174+
175+
func TestBrokenSimpleWriterFinal(t *testing.T) {
176+
t.Parallel()
177+
178+
target := BrokenNIO{From: 2}
179+
180+
require.Error(t,
181+
geany.PrintSimpleWriter(
182+
&target,
183+
nil),
184+
"simple writer no printing error")
185+
}
186+
152187
func TestBrokenLogo(t *testing.T) {
153-
assert.Panics(t,
154-
func() {
155-
_ = geany.PrintLogo("{{ .Geany }", nil)
156-
},
157-
"broken logo does not panic")
188+
t.Parallel()
189+
190+
require.Error(t,
191+
geany.PrintLogo("{{ .Geany }", nil),
192+
"broken logo does produce error")
193+
}
194+
195+
func TestLogoWriterNil(t *testing.T) {
196+
t.Parallel()
197+
198+
require.ErrorIs(t,
199+
geany.PrintLogoWriter(nil, "", nil),
200+
geany.ErrWriterNil,
201+
"nil writer does not produce error")
202+
203+
require.ErrorIs(t,
204+
geany.PrintSimpleWriter(nil, nil),
205+
geany.ErrWriterNil,
206+
"nil writer does not produce error")
158207
}

0 commit comments

Comments
 (0)