Skip to content
This repository was archived by the owner on May 31, 2022. It is now read-only.

Commit 67f4964

Browse files
committed
feat: Allow caller to *not* write to output file. Major restructuring to CommandLineSettings and other classes to support full unit testing.
1 parent 4bc5e3b commit 67f4964

21 files changed

+841
-76
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ To run the unit tests, use the following command line:
6868
6969
```bash
7070
// Assumes that you are inside the cloud-resource-counter folder
71-
$ go test . -v
71+
$ go test
7272
```
7373
7474
## Minimal IAM Policy

accountId_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ func TestGetAccountID(t *testing.T) {
9191
} else if actualAccountID != c.expectedAccountID {
9292
// Does the expected value NOT match the actual value?
9393
t.Errorf("Error: Account returned '%s'; expected %s", actualAccountID, c.expectedAccountID)
94+
} else if mon.ProgramExited {
95+
t.Errorf("Unexpected Exit: The program unexpected exited with status code=%d", mon.ExitCode)
9496
}
9597
}
9698
}

activityMonitor.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ package main
1010
import (
1111
"fmt"
1212
"io"
13-
"os"
1413
"strings"
1514

1615
"github.com/aws/aws-sdk-go/aws/awserr"
@@ -26,12 +25,14 @@ type ActivityMonitor interface {
2625
CheckError(error) bool
2726
ActionError(string, ...interface{})
2827
EndAction(string, ...interface{})
28+
Exit(int)
2929
}
3030

3131
// TerminalActivityMonitor is our terminal-based activity monitor. It allows
3232
// the caller to supply an io.Writer to direct output to.
3333
type TerminalActivityMonitor struct {
3434
io.Writer
35+
ExitFn func(int)
3536
}
3637

3738
// Message constructs a simple message from the format string and arguments
@@ -85,14 +86,23 @@ func (tam *TerminalActivityMonitor) CheckError(err error) bool {
8586
// ActionError formats the supplied format string (and associated parameters) in
8687
// RED and exits the tool.
8788
func (tam *TerminalActivityMonitor) ActionError(format string, v ...interface{}) {
89+
// Display an error message (and newline)
8890
fmt.Fprintln(tam.Writer, color.Red(fmt.Sprintf(format, v...)))
8991
fmt.Fprintln(tam.Writer)
9092

91-
os.Exit(1)
93+
// Exit the program
94+
tam.Exit(1)
9295
}
9396

9497
// EndAction receives a format string (and arguments) and sends to the supplied
9598
// Writer.
9699
func (tam *TerminalActivityMonitor) EndAction(format string, v ...interface{}) {
97100
fmt.Fprintln(tam.Writer, fmt.Sprintf(format, v...))
98101
}
102+
103+
// Exit causes the application to exit
104+
func (tam *TerminalActivityMonitor) Exit(resultCode int) {
105+
if tam.ExitFn != nil {
106+
tam.ExitFn(resultCode)
107+
}
108+
}

activityMonitor_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
"testing"
8+
9+
"github.com/aws/aws-sdk-go/aws/awserr"
10+
)
11+
12+
func TestTerminalActivityMonitorMessage(t *testing.T) {
13+
// Create a builder to hold our contents...
14+
builder := strings.Builder{}
15+
16+
// Create an instance of the Terminal Activity Monitor
17+
mon := TerminalActivityMonitor{
18+
Writer: &builder,
19+
}
20+
21+
// Send it some messages...
22+
mon.Message("Testing %d,%d,%d", 1, 2, 3)
23+
if builder.String() != "Testing 1,2,3" {
24+
t.Errorf("Unexpected message: expected %s, actual %s", "Testing 1,2,3", builder.String())
25+
}
26+
}
27+
28+
func TestTerminalActivityMonitorStartAction(t *testing.T) {
29+
// Create a builder to hold our contents...
30+
builder := strings.Builder{}
31+
32+
// Create an instance of the Terminal Activity Monitor
33+
mon := TerminalActivityMonitor{
34+
Writer: &builder,
35+
}
36+
37+
// Start an action
38+
mon.StartAction("Doing something good")
39+
if builder.String() != " * Doing something good..." {
40+
t.Errorf("Unexpected StartAction: expected %s, actual %s", " * Doing something good...", builder.String())
41+
}
42+
}
43+
44+
func TestTerminalActivityMonitorEndAction(t *testing.T) {
45+
// Create a builder to hold our contents...
46+
builder := strings.Builder{}
47+
48+
// Create an instance of the Terminal Activity Monitor
49+
mon := TerminalActivityMonitor{
50+
Writer: &builder,
51+
}
52+
53+
// Start an action
54+
mon.EndAction("OK (%d)", 123)
55+
if builder.String() != "OK (123)\n" {
56+
t.Errorf("Unexpected EndAction: expected %s, actual %s", "OK (123)\n", builder.String())
57+
}
58+
}
59+
60+
func TestTerminalActivityMonitorCheckError(t *testing.T) {
61+
// Create some test cases with actual errors...
62+
cases := []struct {
63+
Error error
64+
IsEmpty bool
65+
ContainsString string
66+
}{
67+
{
68+
IsEmpty: true,
69+
},
70+
{
71+
Error: errors.New("Something is very wrong"),
72+
ContainsString: "Something is very wrong",
73+
},
74+
{
75+
Error: awserr.New("NoCredentialProviders", "blah", nil),
76+
ContainsString: "profile does not exist",
77+
},
78+
{
79+
Error: awserr.New("AccessDeniedException", "You don't have access to do this stuff\nAnd another thing", nil),
80+
ContainsString: "You don't have access to do this stuff",
81+
},
82+
{
83+
Error: awserr.New("InvalidClientTokenId", "This is a very technical error\nBlah, blah, blah", nil),
84+
ContainsString: "region is not supported",
85+
},
86+
{
87+
Error: awserr.New("SomethingSmellsFishy", "There is something not quite right\nNot really sure what it is", nil),
88+
ContainsString: "SomethingSmellsFishy: There is something not quite right",
89+
},
90+
}
91+
92+
// Loop through the test cases...
93+
for _, c := range cases {
94+
// Create an exit function which simply sets a value
95+
var exitMsg string
96+
exitFn := func(resultCode int) {
97+
exitMsg = fmt.Sprintf("Status %d", resultCode)
98+
}
99+
100+
// Create a builder to hold our contents...
101+
builder := strings.Builder{}
102+
103+
// Create an instance of the Terminal Activity Monitor
104+
mon := TerminalActivityMonitor{
105+
Writer: &builder,
106+
ExitFn: exitFn,
107+
}
108+
109+
// Check for error
110+
mon.CheckError(c.Error)
111+
112+
// Get our actual string
113+
actual := builder.String()
114+
115+
// Do we expect a string?
116+
if c.IsEmpty && actual != "" {
117+
t.Errorf("Unexpected CheckError: expected %s, actual %s", "", actual)
118+
} else if !strings.Contains(actual, c.ContainsString) {
119+
t.Errorf("Unexpected CheckError: expected '%s' to be contained in '%s', but it was not", c.ContainsString, actual)
120+
} else if c.Error != nil && exitMsg != "Status 1" {
121+
t.Errorf("Unexpected CheckError: ExitFn: expected %s, actual %s", "Status 1", exitMsg)
122+
}
123+
}
124+
}

api.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ package main
99

1010
import (
1111
"fmt"
12-
"os"
12+
"io"
1313

1414
"github.com/aws/aws-sdk-go/aws"
1515
"github.com/aws/aws-sdk-go/aws/session"
@@ -193,7 +193,7 @@ type AWSServiceFactory struct {
193193
Session *session.Session
194194
ProfileName string
195195
RegionName string
196-
TraceFile *os.File
196+
TraceWriter io.Writer
197197
}
198198

199199
// Init initializes the AWS service factory by creating an
@@ -202,7 +202,7 @@ type AWSServiceFactory struct {
202202
// tracing (if requested).
203203
func (awssf *AWSServiceFactory) Init() {
204204
// Create an initial configuration object (pointer)
205-
var config *aws.Config = aws.NewConfig()
205+
config := &aws.Config{}
206206

207207
// Was a region specified by the user?
208208
if awssf.RegionName != "" {
@@ -211,27 +211,25 @@ func (awssf *AWSServiceFactory) Init() {
211211
}
212212

213213
// Was tracing specified by the user?
214-
if awssf.TraceFile != nil {
214+
if awssf.TraceWriter != nil {
215215
// Enable logging of AWS Calls with Body
216216
config = config.WithLogLevel(aws.LogDebugWithHTTPBody)
217217

218218
// Enable a logger function which writes to the Trace file
219219
config = config.WithLogger(aws.LoggerFunc(func(args ...interface{}) {
220-
fmt.Fprintln(awssf.TraceFile, args...)
220+
fmt.Fprintln(awssf.TraceWriter, args...)
221221
}))
222222
}
223223

224224
// Construct our session Options object
225-
input := session.Options{
225+
options := session.Options{
226226
SharedConfigState: session.SharedConfigEnable,
227227
Profile: awssf.ProfileName,
228228
Config: *config,
229229
}
230230

231231
// Ensure that we have a session
232-
// It is not clear how a flawed session would manifest itself in this call
233-
// (as there is no error object returned). Perhaps a panic() is raised.
234-
sess := session.Must(session.NewSessionWithOptions(input))
232+
sess := session.Must(session.NewSessionWithOptions(options))
235233

236234
// Does this session have a region? If not, use the default region
237235
if *sess.Config.Region == "" {

0 commit comments

Comments
 (0)