Skip to content

Commit

Permalink
improving the given/when/then parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
mattcorbyeaglen committed Jan 21, 2018
1 parent 94f2d58 commit b9733d4
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 95 deletions.
4 changes: 2 additions & 2 deletions base/some.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ type Some struct {
TestTitle string
interestingGivens testdata.InterestingGivens
capturedIO testdata.CapturedIO
GivenWhenThen string
GivenWhenThen []string
}

//NewSome creates a new Some context. This is an internal function that was exported for testing.
func NewSome(
globalTestingT TestingT,
testTitle string,
testContext *TestMetaData,
givenWhenThen string,
givenWhenThen []string,
givenFunc ...GivenData) *Some {

some := new(Some)
Expand Down
48 changes: 24 additions & 24 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import (
)

func TestMyFirst(testing *testing.T) {
Given(testing, someDataSetup).
Given(testing, theSystemSetup).
When(somethingHappens).
Then(func(testing base.TestingT,
actual testdata.CapturedIO,
givens testdata.InterestingGivens,
) { // passed in testing should be used for assertions
//do assertions
AssertThat(testing, actual["actual"], is.EqualTo("some output"))
})
actual testdata.CapturedIO,
givens testdata.InterestingGivens,
) { // passed in testing should be used for assertions

//we do some assertions here, commenting why
AssertThat(testing, actual["actual"], is.EqualTo("some output"))
})
}

func somethingHappens(actual testdata.CapturedIO, expected testdata.InterestingGivens) {
Expand All @@ -34,14 +35,14 @@ func TestMyFirst_Ranged(testing *testing.T) {
{actual: "a", expected: 1},
}
for _, test := range someRange {
Given(testing, someDataSetup, func(givens testdata.InterestingGivens) {
Given(testing, theSystemSetup, func(givens testdata.InterestingGivens) {
givens["actual"] = test.actual
}).
When(someAction(test)).
When(somethingHappensWithThe(test)).
Then(func(t base.TestingT, actual testdata.CapturedIO, givens testdata.InterestingGivens) {
//do assertions
AssertThat(t, givens["actual"], has.Length(test.expected))
})
//do assertions
AssertThat(t, givens["actual"], has.Length(test.expected))
})
}
}

Expand All @@ -54,30 +55,29 @@ func TestMyFirst_Skipped(tst *testing.T) {
{actual: "a", expected: 1},
}
for _, test := range someRange {
tst.Run(test.actual, func(subT *testing.T) {
Given(subT, someDataSetup, func(givens testdata.InterestingGivens) {
tst.Run(test.actual, func(weAreTesting *testing.T) {
Given(weAreTesting, theSystemSetup, func(givens testdata.InterestingGivens) {
givens["actual"] = test.actual
}).
SkippingThisOneIf(func(someData ...interface{}) bool {
return test.actual == "fff"
}, "some data %s does not work yet", test.actual).
When(someAction(test)).
return test.actual == "fff"
}, "some data %s does not work yet", test.actual).
When(somethingHappensWithThe(test)).
Then(func(t base.TestingT, actual testdata.CapturedIO, givens testdata.InterestingGivens) {
AssertThat(t, test.actual, is.EqualTo("a").Reason("we only want to assert if test actual is a not empty"))
})
AssertThat(t, test.actual, is.EqualTo("a").Reason("we only want to assert if test actual is a not empty"))
})
})
}
}

func TestWithoutGiven(t *testing.T) {
When(t, somethingHappens).
Then(func(testing base.TestingT, actual testdata.CapturedIO, givens testdata.InterestingGivens) {
//do assertions
AssertThat(testing, actual["actual"], is.EqualTo("some output"))
})
AssertThat(testing, actual["actual"], is.EqualTo("some output"))
})
}

func someAction(data struct {
func somethingHappensWithThe(data struct {
actual string
expected int
}) base.CapturedIOGivenData {
Expand All @@ -86,6 +86,6 @@ func someAction(data struct {
}
}

func someDataSetup(givens testdata.InterestingGivens) {
func theSystemSetup(givens testdata.InterestingGivens) {
givens["foofar"] = "faff"
}
2 changes: 1 addition & 1 deletion generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package gogiven
import (
"fmt"
"github.com/corbym/gogiven/generator"
"github.com/corbym/htmlspec"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/corbym/htmlspec"
)

// Generator is a global variable that holds the GoGivensOutputGenerator.
Expand Down
89 changes: 58 additions & 31 deletions given.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package gogiven

import (
"bytes"
"github.com/corbym/gogiven/base"
"github.com/fatih/camelcase"
"go/ast"
"go/format"
"go/parser"
"go/token"
"regexp"
"runtime"
"strings"
"io/ioutil"
)

var globalTestContextMap = newSafeMap()
Expand Down Expand Up @@ -61,31 +60,64 @@ func testTitle(functionName string) string {
return strings.Replace(strings.Join(camelcase.Split(functionName[lastDotInTestName+1:]), " "), "_", " ", -1)
}

// ParseGivenWhenThen parses a test file for the Given/When/Then content of the test in question identified by the parameter "testName"
// ParseGivenWhenThen parses a test file for the Given/When/Then content of the test in question identified by the parameter "testName".
// Returns the content of the function with all metacharacters removed, spaces added to CamelCase and snake case too.
func ParseGivenWhenThen(functionName string, testFileName string) (formattedOutput string) {
func ParseGivenWhenThen(functionName string, testFileName string) (formattedOutput []string) {
split := strings.Split(functionName, ".")
functionName = rawFuncName(functionName, split)
source, _ := ioutil.ReadFile(testFileName)
fset, testFunction, _ := parseFile(testFileName, functionName, source)

givenWhenIndex := positionOfGivenOrWhen(testFunction.Body, fset)
source = source[givenWhenIndex:fset.Position(testFunction.End()).Offset]
interestingStatements := removeAllUninterestingStatements(string(source[:]))
interestingStatements = markupComments(interestingStatements)
splitByLines := strings.Split(interestingStatements, "\n")
formattedOutput = cleanUpGivenWhenThenOutput(splitByLines)
return
}

buffer := new(bytes.Buffer)
func markupComments(source string) (replaced string) {
r := regexp.MustCompile(`(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)`)
replaced = r.ReplaceAllStringFunc(source, func(comment string) string {
return "Noting that " + strings.TrimSpace(strings.Replace(comment, "/", "", -1))
})
return
}

split := strings.Split(functionName, ".")
func cleanUpGivenWhenThenOutput(splitByLines []string) (formattedOutput []string) {
for _, str := range splitByLines {
str = strings.TrimSpace(strings.Join(camelcase.Split(str), " "))
str = strings.Replace(str, "return", "", -1)
str = strings.TrimSpace(replaceAllNonAlphaNumericCharacters(str))
if str != "" {
str = strings.ToUpper(str[:1]) + strings.ToLower(str[1:])
formattedOutput = append(formattedOutput, str)
}
}
return
}

func positionOfGivenOrWhen(currentFuncBody *ast.BlockStmt, fset *token.FileSet) int {
visitor := &IdentVisitor{fset: fset, fileOffsetPos: -1}
ast.Walk(visitor, currentFuncBody)
if visitor.fileOffsetPos == -1 {
panic("could not find position of first given or when statment in func body")
}
return visitor.fileOffsetPos
}

func rawFuncName(functionName string, split []string) string {
functionName = split[len(split)-1]
for i := 2; functionName == "func1"; i++ {
functionName = split[len(split)-i]
}
fset, fun, _ := parseFile(testFileName, functionName)
format.Node(buffer, fset, fun.Body)

formattedOutput = buffer.String()
formattedOutput = formattedOutput[1 : len(formattedOutput)-1]
formattedOutput = strings.Join(camelcase.Split(removeAllUninterestingStatements(formattedOutput)), " ")
formattedOutput = replaceAllNonAlphaNumericCharacters(formattedOutput)
formattedOutput = strings.TrimSpace(strings.Replace(formattedOutput, "\n\t", "\n", -1))
return
return functionName
}

func parseFile(fileName string, functionName string) (fset *token.FileSet, fun *ast.FuncDecl, error error) {
func parseFile(fileName string, functionName string, src []byte) (fset *token.FileSet, fun *ast.FuncDecl, error error) {
fset = token.NewFileSet()
if file, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments); err == nil {
if file, err := parser.ParseFile(fset, fileName, src, parser.ParseComments); err == nil {
for _, d := range file.Decls {
if f, ok := d.(*ast.FuncDecl); ok && f.Name.Name == functionName {
fun = f
Expand All @@ -97,12 +129,7 @@ func parseFile(fileName string, functionName string) (fset *token.FileSet, fun *
}

func removeAllUninterestingStatements(content string) (removed string) {
index := strings.Index(content, "Given")
if index == -1 {
index = strings.Index(content, "When")
}
removed = content[index:]
removed = removeDeclarations(removed, "interface", "{", "}")
removed = removeDeclarations(content, "interface", "{", "}")
removed = removeDeclarations(removed, "func", "(", ")")
return
}
Expand Down Expand Up @@ -135,14 +162,14 @@ func findBalancedBracketFor(remove string, openBracket string, closeBracket stri
}

func replaceAllNonAlphaNumericCharacters(replace string) (replaced string) {
r := regexp.MustCompile("(?sm:([^a-zA-Z0-9*!£$%+\\-^\"= \\r\\n\\t<>]))")
replace = r.ReplaceAllString(replace, "")
r = regexp.MustCompile(" +")
replace = r.ReplaceAllString(replace, " ")
r = regexp.MustCompile("\t\t+")
replace = r.ReplaceAllString(replace, "\t\t")
r = regexp.MustCompile("[\r\n]+")
replaced = r.ReplaceAllString(replace, "\r\n")
r := regexp.MustCompile("(?sm:([^a-zA-Z0-9*!£$%+/\\-^\"= \\r\\n\\t<>]))")
replaced = r.ReplaceAllString(replace, "")
r = regexp.MustCompile("\\s+")
replaced = r.ReplaceAllString(replaced, " ")
r = regexp.MustCompile(`".*?"`)
replaced = r.ReplaceAllStringFunc(replaced, func(quoted string) string {
return "\"" + strings.TrimSpace(strings.Replace(quoted, "\"", "", -1)) + "\""
})
return
}

Expand Down
Loading

0 comments on commit b9733d4

Please sign in to comment.