Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

grains: add generator #959

Merged
merged 2 commits into from
Nov 16, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions exercises/grains/.meta/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

import (
"encoding/json"
"log"
"strings"
"text/template"

"../../../gen"
)

func main() {
t, err := template.New("").Parse(tmpl)
if err != nil {
log.Fatal(err)
}
var j js
if err := gen.Gen("grains", &j, t); err != nil {
log.Fatal(err)
}
}

// The JSON structure we expect to be able to unmarshal into
type js struct {
Groups []testGroup `json:"Cases"`
}

type testGroup struct {
Description string
Cases []json.RawMessage `property:"RAW"`
SquareCases []SquareCase `property:"square"`
// Note: canonical-data.json has a element of "cases"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you or @ferhatelmas encountered an other canonical-data files with a cases.cases structure like this one? It seems odd. Wonder if there may be a better way to represent the data in that file we could suggest upstream.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The are several which have nested cases.cases but only one property value; these are not a problem. Others also have differing property values, which make it a little harder to handle. Two exercises, custom-set and variable-length-quantity have generators that use a PropertyMatch technique. Four others use the RAW special handling with classifyByProperty (allergies, run-length-encoding, clock, and grains). A different structuring in canonical data could help to generate separate test case arrays for different property values. A change would require a retrofit for current generators however.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I first saw, I was shocked but then checked json schema in problem-specification and it was valid. Thus, even if it's harder to handle, thought that was the decision and tried to live with.

// which includes a test for property 'total', but it is ignored here,
// since "expected" is a single known value.
}

type SquareCase struct {
Description string
Input int
Expected interface{}
}

func (c SquareCase) Valid() bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps HasAnswer is a more descriptive term for this? It took me a second to realize it was the error check for Answer 😄. Not having an answer is a valid test case, just that it is looking for an error rather than an answer value? It makes that if check in the template below straightforward.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, HasAnswer is more descriptive.

valid, _ := determineExpected(c.Expected)
return valid
}

func (c SquareCase) Answer() uint64 {
_, answer := determineExpected(c.Expected)
return answer
}

func (c SquareCase) EditedDescription() string {
// Go doesn't raise exceptions, so replace the wording in .Description.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this is worth bringing up in problem-specifications? Exception seems to be the narrower term here, while Error is the more general. If anything exception based languages should be rewriting these descriptions if necessary. I've always considered Exceptions to be a sub-term for Errors in generic conversation, but not the other way around.

Copy link
Contributor Author

@leenipper leenipper Nov 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I was actually surprised to see the exception wording in the canonical data file. I thought it was very tilted toward languages that use exceptions. Error is a better term to use there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed here.

return strings.Replace(c.Description, "raises an exception", "returns an error", 1)
}

// determineExpected examines an .Expected interface{} object and determines
// whether a test case is valid(bool) and has an answer or expects an error.
// returning valid and answer.
func determineExpected(expected interface{}) (bool, uint64) {
ans, ok := expected.(float64)
if ok {
if ans == float64(-1) {
return false, 0
}
return ok, uint64(ans)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my own curiousity, why not return true instead of ok here? The ok val is a reuse of the ok val for a different expression which is (very slightly) confusing and it has to be true to get here anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agree.

}
return false, 0
}

func (groups testGroup) GroupShortName() string {
return strings.ToLower(strings.Fields(groups.Description)[0])
}

var tmpl = `package grains

{{.Header}}

{{range .J.Groups}}
{{- if .SquareCases }}
// {{ .Description }}
var squareTests = []struct {
description string
input int
expectedVal uint64
expectError bool
}{
{{- range .SquareCases}}
{
description: "{{.EditedDescription}}",
input: {{.Input}},
{{- if .Valid}}
expectedVal: {{.Answer}},
{{- else}}
expectError: true,
{{- end}}
},
{{- end }}
}
{{- end }}
{{end}}
`
64 changes: 64 additions & 0 deletions exercises/grains/cases_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package grains

// Source: exercism/problem-specifications
// Commit: d4554e6 grains: fix a typo in comments of canonical data
// Problem Specifications Version: 1.0.0

// returns the number of grains on the square
var squareTests = []struct {
description string
input int
expectedVal uint64
expectError bool
}{
{
description: "1",
input: 1,
expectedVal: 1,
},
{
description: "2",
input: 2,
expectedVal: 2,
},
{
description: "3",
input: 3,
expectedVal: 4,
},
{
description: "4",
input: 4,
expectedVal: 8,
},
{
description: "16",
input: 16,
expectedVal: 32768,
},
{
description: "32",
input: 32,
expectedVal: 2147483648,
},
{
description: "64",
input: 64,
expectedVal: 9223372036854775808,
},
{
description: "square 0 returns an error",
input: 0,
expectError: true,
},
{
description: "negative square returns an error",
input: -1,
expectError: true,
},
{
description: "square greater than 64 returns an error",
input: 65,
expectError: true,
},
}
24 changes: 4 additions & 20 deletions exercises/grains/grains_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,25 @@ import (
"testing"
)

var squareTests = []struct {
input int
expectedVal uint64
expectError bool
}{
{1, 1, false},
{2, 2, false},
{3, 4, false},
{4, 8, false},
{16, 32768, false},
{32, 2147483648, false},
{64, 9223372036854775808, false},
{65, 0, true},
{0, 0, true},
{-1, 0, true},
}

func TestSquare(t *testing.T) {
for _, test := range squareTests {
actualVal, actualErr := Square(test.input)

// check actualVal only if no error expected
if !test.expectError && actualVal != test.expectedVal {
t.Errorf("Square(%d) expected %d, Actual %d", test.input, test.expectedVal, actualVal)
t.Fatalf("FAIL: %s\nSquare(%d) expected %d, Actual %d", test.description, test.input, test.expectedVal, actualVal)
}

// if we expect an error and there isn't one
if test.expectError && actualErr == nil {
t.Errorf("Square(%d) expected an error, but error is nil", test.input)
t.Fatalf("FAIL: %s\nSquare(%d) expected an error, but error is nil", test.description, test.input)
}
// if we don't expect an error and there is one
if !test.expectError && actualErr != nil {
var _ error = actualErr
t.Errorf("Square(%d) expected no error, but error is: %s", test.input, actualErr)
t.Fatalf("FAIL: %s\nSquare(%d) expected no error, but error is: %s", test.description, test.input, actualErr)
}
t.Logf("PASS: %s", test.description)
}
}

Expand Down