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

Commit 8cfca67

Browse files
author
Saketram Durbha
authored
add readme parsing tests (#25)
1 parent 30d14fe commit 8cfca67

File tree

4 files changed

+505
-18
lines changed

4 files changed

+505
-18
lines changed

internal/lifecycle/lifecycle.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import (
1818
"errors"
1919
"fmt"
2020
"github.com/GoogleCloudPlatform/serverless-sample-tester/internal/util"
21+
"github.com/spf13/viper"
2122
"log"
2223
"os"
2324
"os/exec"
2425
"path/filepath"
25-
"github.com/spf13/viper"
2626
)
2727

2828
// Lifecycle is a list of ordered exec.Cmd that should be run to execute a certain process.
@@ -58,8 +58,6 @@ func NewLifecycle(sampleDir, serviceName, gcrURL string) (Lifecycle, error) {
5858
readmePath = filepath.Join(sampleDir, "README.md")
5959
}
6060

61-
62-
6361
if _, err := os.Stat(readmePath); err == nil {
6462
lifecycle, err := parseREADME(readmePath, serviceName, gcrURL)
6563
// Show README location
@@ -69,11 +67,11 @@ func NewLifecycle(sampleDir, serviceName, gcrURL string) (Lifecycle, error) {
6967
return lifecycle, nil
7068
}
7169

72-
if !errors.Is(err, errNoREADMECodeBlocksFound) {
70+
if !errors.Is(err, errNoReadmeCodeBlocksFound) {
7371
return nil, fmt.Errorf("lifecycle.parseREADME: %s: %w", readmePath, err)
7472
}
7573

76-
log.Println("No code blocks immediately preceded by %s found in README.md\n", codeTag)
74+
log.Printf("No code blocks immediately preceded by %s found in README.md\n", codeTag)
7775
} else {
7876
log.Println("No README.md found")
7977
}

internal/lifecycle/readme.go

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ var (
4242

4343
mdCodeFenceStartRegexp = regexp.MustCompile("^\\w*`{3,}[^`]*$")
4444

45-
errNoREADMECodeBlocksFound = fmt.Errorf("lifecycle.extractCodeBlocks: no code blocks immediately preceded by %s found", codeTag)
45+
errNoReadmeCodeBlocksFound = fmt.Errorf("lifecycle.extractCodeBlocks: no code blocks immediately preceded by %s found", codeTag)
46+
errCodeBlockNotClosed = fmt.Errorf("unexpected EOF: code block not closed")
47+
errCodeBlockStartNotFound = fmt.Errorf("expecting start of code block immediately after code tag")
48+
errEOFAfterCodeTag = fmt.Errorf("unexpected EOF: file ended immediately after code tag")
49+
errCodeBlockEndAfterLineCont = "end of code block: expecting command line continuation"
4650
)
4751

4852
// codeBlock is a slice of strings containing terminal commands. codeBlocks, for example, could be used to hold the
@@ -68,7 +72,7 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error)
6872

6973
i++
7074
if i >= len(cb) {
71-
return nil, fmt.Errorf("unexpected end of code block: expecting command line continuation; code block dump:\n%s", strings.Join(cb, "\n"))
75+
return nil, fmt.Errorf("%s; code block dump:\n%s", errCodeBlockEndAfterLineCont, strings.Join(cb, "\n"))
7276
}
7377

7478
l := cb[i]
@@ -98,9 +102,10 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error)
98102
return cmds, nil
99103
}
100104

101-
// parseREADME parses a README file with the given name. It reads terminal commands surrounded by one of the codeTags
102-
// listed above and loads them into a Lifecycle. In the process, it replaces the Cloud Run service name and Container
103-
// Registry tag with the provided inputs.
105+
// parseREADME parses a README file with the given name. It parses terminal commands in code blocks annotated by the
106+
// codeTag and loads them into a Lifecycle. In the process, it replaces the Cloud Run service name and Container
107+
// Registry tag with the provided inputs. It also expands environment variables and supports bash-style line
108+
// continuations.
104109
func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) {
105110
file, err := os.Open(filename)
106111
if err != nil {
@@ -109,20 +114,29 @@ func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) {
109114
defer file.Close()
110115

111116
scanner := bufio.NewScanner(file)
117+
118+
return extractLifecycle(scanner, serviceName, gcrURL)
119+
}
120+
121+
// extractLifecycle is a helper function for parseREADME. It takes a scanner that reads from a Markdown file and parses
122+
// terminal commands in code blocks annotated by the codeTag and loads them into a Lifecycle. In the process, it
123+
// replaces the Cloud Run service name and Container Registry tag with the provided inputs. It also expands environment
124+
// variables and supports bash-style line continuations.
125+
func extractLifecycle(scanner *bufio.Scanner, serviceName, gcrURL string) (Lifecycle, error) {
112126
codeBlocks, err := extractCodeBlocks(scanner)
113127
if err != nil {
114-
return nil, fmt.Errorf("lifecycle.extractCodeBlocks: %s: %w", filename, err)
128+
return nil, fmt.Errorf("lifecycle.extractCodeBlocks: %w", err)
115129
}
116130

117131
if len(codeBlocks) == 0 {
118-
return nil, errNoREADMECodeBlocksFound
132+
return nil, errNoReadmeCodeBlocksFound
119133
}
120134

121135
var l Lifecycle
122136
for _, b := range codeBlocks {
123137
cmds, err := b.toCommands(serviceName, gcrURL)
124138
if err != nil {
125-
return l, fmt.Errorf("codeBlock.toCommands: code blocks in %s: %w", filename, err)
139+
return l, fmt.Errorf("codeBlock.toCommands: %w", err)
126140
}
127141

128142
l = append(l, cmds...)
@@ -147,14 +161,14 @@ func extractCodeBlocks(scanner *bufio.Scanner) ([]codeBlock, error) {
147161
if err := scanner.Err(); err != nil {
148162
return nil, fmt.Errorf("line %d: bufio.Scanner.Scan: %w", lineNum, err)
149163
}
150-
return nil, fmt.Errorf("unexpected EOF: file ended immediately after code tag")
164+
return nil, errEOFAfterCodeTag
151165
}
152166
lineNum++
153167

154168
startCodeBlockLine := scanner.Text()
155169
m := mdCodeFenceStartRegexp.MatchString(startCodeBlockLine)
156170
if !m {
157-
return nil, fmt.Errorf("line %d: expecting start of code block immediately after code tag", lineNum)
171+
return nil, fmt.Errorf("line %d: %w", lineNum, errCodeBlockStartNotFound)
158172
}
159173

160174
c := strings.Count(startCodeBlockLine, "`")
@@ -178,7 +192,7 @@ func extractCodeBlocks(scanner *bufio.Scanner) ([]codeBlock, error) {
178192
}
179193

180194
if !blockClosed {
181-
return nil, fmt.Errorf("unexpected EOF: code block not closed")
195+
return nil, errCodeBlockNotClosed
182196
}
183197

184198
blocks = append(blocks, block)
@@ -204,15 +218,15 @@ func replaceServiceName(command, serviceName string) string {
204218
sp := strings.Split(command, " ")
205219

206220
// Detects if the user specified the Cloud Run service name in an environment variable
207-
for i := 0; i < len(sp); i++ {
221+
for i := 0; i < len(sp); i++ {
208222
if sp[i] == os.ExpandEnv("$CLOUD_RUN_SERVICE_NAME") {
209223
sp[i] = serviceName
210224
return strings.Join(sp, " ")
211225
}
212226
}
213227

214228
// Searches for specific gcloud keywords and takes service name from them
215-
for i := 0; i < len(sp) - 1; i++ {
229+
for i := 0; i < len(sp)-1; i++ {
216230
if sp[i] == "deploy" || sp[i] == "update" {
217231
sp[i+1] = serviceName
218232
return strings.Join(sp, " ")

0 commit comments

Comments
 (0)