use reflection to set step arguments #9
* 9226bc5 more expressive conversion errors
Этот коммит содержится в:
		
							родитель
							
								
									d7334fd66e
								
							
						
					
					
						коммит
						2046da1611
					
				
					 5 изменённых файлов: 226 добавлений и 228 удалений
				
			
		
							
								
								
									
										14
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
										
									
									
									
								
							|  | @ -79,19 +79,19 @@ func (c *GodogCart) resetReserve(interface{}) { | ||||||
| 	c.reserve = 0 | 	c.reserve = 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *GodogCart) thereAreNumGodogsInReserve(args ...*godog.Arg) error { | func (c *GodogCart) thereAreNumGodogsInReserve(avail int) error { | ||||||
| 	c.reserve = args[0].Int() | 	c.reserve = avail | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *GodogCart) iEatNum(args ...*godog.Arg) error { | func (c *GodogCart) iEatNum(num int) error { | ||||||
| 	c.Eat(args[0].Int()) | 	c.Eat(num) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *GodogCart) thereShouldBeNumRemaining(args ...*godog.Arg) error { | func (c *GodogCart) thereShouldBeNumRemaining(left int) error { | ||||||
| 	if c.Available() != args[0].Int() { | 	if c.Available() != left { | ||||||
| 		return fmt.Errorf("expected %d godogs to be remaining, but there is %d", args[0].Int(), c.Available()) | 		return fmt.Errorf("expected %d godogs to be remaining, but there is %d", left, c.Available()) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										143
									
								
								arguments.go
									
										
									
									
									
								
							
							
						
						
									
										143
									
								
								arguments.go
									
										
									
									
									
								
							|  | @ -1,143 +0,0 @@ | ||||||
| package godog |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"strconv" |  | ||||||
| 
 |  | ||||||
| 	"github.com/cucumber/gherkin-go" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Arg is an argument for StepHandler parsed from |  | ||||||
| // the regexp submatch to handle the step. |  | ||||||
| // |  | ||||||
| // In future versions, it may be replaced with |  | ||||||
| // an argument injection toolkit using reflect |  | ||||||
| // package. |  | ||||||
| type Arg struct { |  | ||||||
| 	value interface{} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // StepArgument func creates a step argument. |  | ||||||
| // used in cases when calling another step from |  | ||||||
| // within a StepHandler function. |  | ||||||
| func StepArgument(value interface{}) *Arg { |  | ||||||
| 	return &Arg{value: value} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Float64 converts an argument to float64 |  | ||||||
| // or panics if unable to convert it |  | ||||||
| func (a *Arg) Float64() float64 { |  | ||||||
| 	s, ok := a.value.(string) |  | ||||||
| 	a.must(ok, "string") |  | ||||||
| 	v, err := strconv.ParseFloat(s, 64) |  | ||||||
| 	if err == nil { |  | ||||||
| 		return v |  | ||||||
| 	} |  | ||||||
| 	panic(fmt.Sprintf(`cannot convert "%s" to float64: %s`, s, err)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Float32 converts an argument to float32 |  | ||||||
| // or panics if unable to convert it |  | ||||||
| func (a *Arg) Float32() float32 { |  | ||||||
| 	s, ok := a.value.(string) |  | ||||||
| 	a.must(ok, "string") |  | ||||||
| 	v, err := strconv.ParseFloat(s, 32) |  | ||||||
| 	if err == nil { |  | ||||||
| 		return float32(v) |  | ||||||
| 	} |  | ||||||
| 	panic(fmt.Sprintf(`cannot convert "%s" to float32: %s`, s, err)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Int converts an argument to int |  | ||||||
| // or panics if unable to convert it |  | ||||||
| func (a *Arg) Int() int { |  | ||||||
| 	s, ok := a.value.(string) |  | ||||||
| 	a.must(ok, "string") |  | ||||||
| 	v, err := strconv.ParseInt(s, 10, 0) |  | ||||||
| 	if err == nil { |  | ||||||
| 		return int(v) |  | ||||||
| 	} |  | ||||||
| 	panic(fmt.Sprintf(`cannot convert "%s" to int: %s`, s, err)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Int64 converts an argument to int64 |  | ||||||
| // or panics if unable to convert it |  | ||||||
| func (a *Arg) Int64() int64 { |  | ||||||
| 	s, ok := a.value.(string) |  | ||||||
| 	a.must(ok, "string") |  | ||||||
| 	v, err := strconv.ParseInt(s, 10, 64) |  | ||||||
| 	if err == nil { |  | ||||||
| 		return v |  | ||||||
| 	} |  | ||||||
| 	panic(fmt.Sprintf(`cannot convert "%s" to int64: %s`, s, err)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Int32 converts an argument to int32 |  | ||||||
| // or panics if unable to convert it |  | ||||||
| func (a *Arg) Int32() int32 { |  | ||||||
| 	s, ok := a.value.(string) |  | ||||||
| 	a.must(ok, "string") |  | ||||||
| 	v, err := strconv.ParseInt(s, 10, 32) |  | ||||||
| 	if err == nil { |  | ||||||
| 		return int32(v) |  | ||||||
| 	} |  | ||||||
| 	panic(fmt.Sprintf(`cannot convert "%s" to int32: %s`, s, err)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Int16 converts an argument to int16 |  | ||||||
| // or panics if unable to convert it |  | ||||||
| func (a *Arg) Int16() int16 { |  | ||||||
| 	s, ok := a.value.(string) |  | ||||||
| 	a.must(ok, "string") |  | ||||||
| 	v, err := strconv.ParseInt(s, 10, 16) |  | ||||||
| 	if err == nil { |  | ||||||
| 		return int16(v) |  | ||||||
| 	} |  | ||||||
| 	panic(fmt.Sprintf(`cannot convert "%s" to int16: %s`, s, err)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Int8 converts an argument to int8 |  | ||||||
| // or panics if unable to convert it |  | ||||||
| func (a *Arg) Int8() int8 { |  | ||||||
| 	s, ok := a.value.(string) |  | ||||||
| 	a.must(ok, "string") |  | ||||||
| 	v, err := strconv.ParseInt(s, 10, 8) |  | ||||||
| 	if err == nil { |  | ||||||
| 		return int8(v) |  | ||||||
| 	} |  | ||||||
| 	panic(fmt.Sprintf(`cannot convert "%s" to int8: %s`, s, err)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // String converts an argument to string |  | ||||||
| func (a *Arg) String() string { |  | ||||||
| 	s, ok := a.value.(string) |  | ||||||
| 	a.must(ok, "string") |  | ||||||
| 	return s |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Bytes converts an argument string to bytes |  | ||||||
| func (a *Arg) Bytes() []byte { |  | ||||||
| 	s, ok := a.value.(string) |  | ||||||
| 	a.must(ok, "string") |  | ||||||
| 	return []byte(s) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DocString converts an argument to *gherkin.DocString node |  | ||||||
| func (a *Arg) DocString() *gherkin.DocString { |  | ||||||
| 	s, ok := a.value.(*gherkin.DocString) |  | ||||||
| 	a.must(ok, "*gherkin.DocString") |  | ||||||
| 	return s |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DataTable converts an argument to *gherkin.DataTable node |  | ||||||
| func (a *Arg) DataTable() *gherkin.DataTable { |  | ||||||
| 	s, ok := a.value.(*gherkin.DataTable) |  | ||||||
| 	a.must(ok, "*gherkin.DataTable") |  | ||||||
| 	return s |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (a *Arg) must(ok bool, expected string) { |  | ||||||
| 	if !ok { |  | ||||||
| 		panic(fmt.Sprintf(`cannot convert "%v" of type "%T" to type "%s"`, a.value, a.value, expected)) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -8,6 +8,7 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/DATA-DOG/godog" | 	"github.com/DATA-DOG/godog" | ||||||
|  | 	"github.com/cucumber/gherkin-go" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type lsFeature struct { | type lsFeature struct { | ||||||
|  | @ -24,29 +25,29 @@ func lsFeatureContext(s godog.Suite) { | ||||||
| 	s.Step(`^I should get output:$`, c.iShouldGetOutput) | 	s.Step(`^I should get output:$`, c.iShouldGetOutput) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (f *lsFeature) iAmInDirectory(args ...*godog.Arg) error { | func (f *lsFeature) iAmInDirectory(name string) error { | ||||||
| 	f.dir = os.TempDir() + "/" + args[0].String() | 	f.dir = os.TempDir() + "/" + name | ||||||
| 	if err := os.RemoveAll(f.dir); err != nil && !os.IsNotExist(err) { | 	if err := os.RemoveAll(f.dir); err != nil && !os.IsNotExist(err) { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	return os.Mkdir(f.dir, 0775) | 	return os.Mkdir(f.dir, 0775) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (f *lsFeature) iHaveFileOrDirectoryNamed(args ...*godog.Arg) (err error) { | func (f *lsFeature) iHaveFileOrDirectoryNamed(typ, name string) (err error) { | ||||||
| 	if len(f.dir) == 0 { | 	if len(f.dir) == 0 { | ||||||
| 		return fmt.Errorf("the directory was not chosen yet") | 		return fmt.Errorf("the directory was not chosen yet") | ||||||
| 	} | 	} | ||||||
| 	switch args[0].String() { | 	switch typ { | ||||||
| 	case "file": | 	case "file": | ||||||
| 		err = ioutil.WriteFile(f.dir+"/"+args[1].String(), []byte{}, 0664) | 		err = ioutil.WriteFile(f.dir+"/"+name, []byte{}, 0664) | ||||||
| 	case "directory": | 	case "directory": | ||||||
| 		err = os.Mkdir(f.dir+"/"+args[1].String(), 0775) | 		err = os.Mkdir(f.dir+"/"+name, 0775) | ||||||
| 	} | 	} | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (f *lsFeature) iShouldGetOutput(args ...*godog.Arg) error { | func (f *lsFeature) iShouldGetOutput(names *gherkin.DocString) error { | ||||||
| 	expected := strings.Split(args[0].DocString().Content, "\n") | 	expected := strings.Split(names.Content, "\n") | ||||||
| 	actual := strings.Split(strings.TrimSpace(f.buf.String()), "\n") | 	actual := strings.Split(strings.TrimSpace(f.buf.String()), "\n") | ||||||
| 	if len(expected) != len(actual) { | 	if len(expected) != len(actual) { | ||||||
| 		return fmt.Errorf("number of expected output lines %d, does not match actual: %d", len(expected), len(actual)) | 		return fmt.Errorf("number of expected output lines %d, does not match actual: %d", len(expected), len(actual)) | ||||||
|  | @ -59,7 +60,7 @@ func (f *lsFeature) iShouldGetOutput(args ...*godog.Arg) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (f *lsFeature) iRunLs(args ...*godog.Arg) error { | func (f *lsFeature) iRunLs() error { | ||||||
| 	f.buf.Reset() | 	f.buf.Reset() | ||||||
| 	return ls(f.dir, f.buf) | 	return ls(f.dir, f.buf) | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										201
									
								
								suite.go
									
										
									
									
									
								
							
							
						
						
									
										201
									
								
								suite.go
									
										
									
									
									
								
							|  | @ -13,28 +13,13 @@ import ( | ||||||
| 	"github.com/cucumber/gherkin-go" | 	"github.com/cucumber/gherkin-go" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | var errorInterface = reflect.TypeOf((*error)(nil)).Elem() | ||||||
|  | 
 | ||||||
| type feature struct { | type feature struct { | ||||||
| 	*gherkin.Feature | 	*gherkin.Feature | ||||||
| 	Path string `json:"path"` | 	Path string `json:"path"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Regexp is an unified type for regular expression |  | ||||||
| // it can be either a string or a *regexp.Regexp |  | ||||||
| type Regexp interface{} |  | ||||||
| 
 |  | ||||||
| // StepHandler is a func to handle the step |  | ||||||
| // |  | ||||||
| // The handler receives all arguments which |  | ||||||
| // will be matched according to the Regexp |  | ||||||
| // which is passed with a step registration. |  | ||||||
| // |  | ||||||
| // The error in return - represents a reason of failure. |  | ||||||
| // All consequent scenario steps are skipped. |  | ||||||
| // |  | ||||||
| // Returning signals that the step has finished and that |  | ||||||
| // the feature runner can move on to the next step. |  | ||||||
| type StepHandler func(...*Arg) error |  | ||||||
| 
 |  | ||||||
| // ErrUndefined is returned in case if step definition was not found | // ErrUndefined is returned in case if step definition was not found | ||||||
| var ErrUndefined = fmt.Errorf("step is undefined") | var ErrUndefined = fmt.Errorf("step is undefined") | ||||||
| 
 | 
 | ||||||
|  | @ -47,9 +32,133 @@ var ErrUndefined = fmt.Errorf("step is undefined") | ||||||
| // when step is matched and is either failed | // when step is matched and is either failed | ||||||
| // or successful | // or successful | ||||||
| type StepDef struct { | type StepDef struct { | ||||||
| 	Args    []*Arg | 	args    []interface{} | ||||||
| 	Handler StepHandler | 	hv      reflect.Value | ||||||
| 	Expr    *regexp.Regexp | 	Expr    *regexp.Regexp | ||||||
|  | 	Handler interface{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sd *StepDef) run() error { | ||||||
|  | 	typ := sd.hv.Type() | ||||||
|  | 	if len(sd.args) < typ.NumIn() { | ||||||
|  | 		return fmt.Errorf("func expects %d arguments, which is more than %d matched from step", typ.NumIn(), len(sd.args)) | ||||||
|  | 	} | ||||||
|  | 	var values []reflect.Value | ||||||
|  | 	for i := 0; i < typ.NumIn(); i++ { | ||||||
|  | 		param := typ.In(i) | ||||||
|  | 		switch param.Kind() { | ||||||
|  | 		case reflect.Int: | ||||||
|  | 			s, err := sd.shouldBeString(i) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			v, err := strconv.ParseInt(s, 10, 0) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf(`cannot convert argument %d: "%s" to int: %s`, i, s, err) | ||||||
|  | 			} | ||||||
|  | 			values = append(values, reflect.ValueOf(int(v))) | ||||||
|  | 		case reflect.Int64: | ||||||
|  | 			s, err := sd.shouldBeString(i) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			v, err := strconv.ParseInt(s, 10, 64) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf(`cannot convert argument %d: "%s" to int64: %s`, i, s, err) | ||||||
|  | 			} | ||||||
|  | 			values = append(values, reflect.ValueOf(int64(v))) | ||||||
|  | 		case reflect.Int32: | ||||||
|  | 			s, err := sd.shouldBeString(i) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			v, err := strconv.ParseInt(s, 10, 32) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf(`cannot convert argument %d: "%s" to int32: %s`, i, s, err) | ||||||
|  | 			} | ||||||
|  | 			values = append(values, reflect.ValueOf(int32(v))) | ||||||
|  | 		case reflect.Int16: | ||||||
|  | 			s, err := sd.shouldBeString(i) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			v, err := strconv.ParseInt(s, 10, 16) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf(`cannot convert argument %d: "%s" to int16: %s`, i, s, err) | ||||||
|  | 			} | ||||||
|  | 			values = append(values, reflect.ValueOf(int16(v))) | ||||||
|  | 		case reflect.Int8: | ||||||
|  | 			s, err := sd.shouldBeString(i) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			v, err := strconv.ParseInt(s, 10, 8) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf(`cannot convert argument %d: "%s" to int8: %s`, i, s, err) | ||||||
|  | 			} | ||||||
|  | 			values = append(values, reflect.ValueOf(int8(v))) | ||||||
|  | 		case reflect.String: | ||||||
|  | 			s, err := sd.shouldBeString(i) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			values = append(values, reflect.ValueOf(s)) | ||||||
|  | 		case reflect.Float64: | ||||||
|  | 			s, err := sd.shouldBeString(i) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			v, err := strconv.ParseFloat(s, 64) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf(`cannot convert argument %d: "%s" to float64: %s`, i, s, err) | ||||||
|  | 			} | ||||||
|  | 			values = append(values, reflect.ValueOf(v)) | ||||||
|  | 		case reflect.Float32: | ||||||
|  | 			s, err := sd.shouldBeString(i) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			v, err := strconv.ParseFloat(s, 32) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf(`cannot convert argument %d: "%s" to float32: %s`, i, s, err) | ||||||
|  | 			} | ||||||
|  | 			values = append(values, reflect.ValueOf(float32(v))) | ||||||
|  | 		case reflect.Ptr: | ||||||
|  | 			arg := sd.args[i] | ||||||
|  | 			switch param.Elem().String() { | ||||||
|  | 			case "gherkin.DocString": | ||||||
|  | 				v, ok := arg.(*gherkin.DocString) | ||||||
|  | 				if !ok { | ||||||
|  | 					return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *gherkin.DocString`, i, arg, arg) | ||||||
|  | 				} | ||||||
|  | 				values = append(values, reflect.ValueOf(v)) | ||||||
|  | 			case "gherkin.DataTable": | ||||||
|  | 				v, ok := arg.(*gherkin.DataTable) | ||||||
|  | 				if !ok { | ||||||
|  | 					return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *gherkin.DocString`, i, arg, arg) | ||||||
|  | 				} | ||||||
|  | 				values = append(values, reflect.ValueOf(v)) | ||||||
|  | 			default: | ||||||
|  | 				return fmt.Errorf("the argument %d type %T is not supported", i, arg) | ||||||
|  | 			} | ||||||
|  | 		default: | ||||||
|  | 			return fmt.Errorf("the argument %d type %s is not supported", i, param.Kind()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	ret := sd.hv.Call(values)[0].Interface() | ||||||
|  | 	if nil == ret { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return ret.(error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sd *StepDef) shouldBeString(idx int) (string, error) { | ||||||
|  | 	arg := sd.args[idx] | ||||||
|  | 	s, ok := arg.(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "", fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to string`, idx, arg, arg) | ||||||
|  | 	} | ||||||
|  | 	return s, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Suite is an interface which allows various contexts | // Suite is an interface which allows various contexts | ||||||
|  | @ -64,14 +173,36 @@ type StepDef struct { | ||||||
| // executions are catching panic error since it may | // executions are catching panic error since it may | ||||||
| // be a context specific error. | // be a context specific error. | ||||||
| type Suite interface { | type Suite interface { | ||||||
|  | 	// Run the test suite | ||||||
| 	Run() | 	Run() | ||||||
| 	Step(expr Regexp, h StepHandler) | 
 | ||||||
| 	// suite events | 	// Registers a step which will execute stepFunc | ||||||
|  | 	// on step expr match | ||||||
|  | 	// | ||||||
|  | 	// expr can be either a string or a *regexp.Regexp | ||||||
|  | 	// stepFunc is a func to handle the step, arguments | ||||||
|  | 	// are set from matched step | ||||||
|  | 	Step(expr interface{}, h interface{}) | ||||||
|  | 
 | ||||||
|  | 	// BeforeSuite registers a func to run on initial | ||||||
|  | 	// suite startup | ||||||
| 	BeforeSuite(f func()) | 	BeforeSuite(f func()) | ||||||
|  | 
 | ||||||
|  | 	// BeforeScenario registers a func to run before | ||||||
|  | 	// every *gherkin.Scenario or *gherkin.ScenarioOutline | ||||||
| 	BeforeScenario(f func(interface{})) | 	BeforeScenario(f func(interface{})) | ||||||
|  | 
 | ||||||
|  | 	// BeforeStep register a handler before every step | ||||||
| 	BeforeStep(f func(*gherkin.Step)) | 	BeforeStep(f func(*gherkin.Step)) | ||||||
|  | 
 | ||||||
|  | 	// AfterStep register a handler after every step | ||||||
| 	AfterStep(f func(*gherkin.Step, error)) | 	AfterStep(f func(*gherkin.Step, error)) | ||||||
|  | 
 | ||||||
|  | 	// AfterScenario registers a func to run after | ||||||
|  | 	// every *gherkin.Scenario or *gherkin.ScenarioOutline | ||||||
| 	AfterScenario(f func(interface{}, error)) | 	AfterScenario(f func(interface{}, error)) | ||||||
|  | 
 | ||||||
|  | 	// AfterSuite runs func int the end of tests | ||||||
| 	AfterSuite(f func()) | 	AfterSuite(f func()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -117,7 +248,7 @@ func New() Suite { | ||||||
| // | // | ||||||
| // If none of the StepHandlers are matched, then | // If none of the StepHandlers are matched, then | ||||||
| // ErrUndefined error will be returned. | // ErrUndefined error will be returned. | ||||||
| func (s *suite) Step(expr Regexp, h StepHandler) { | func (s *suite) Step(expr interface{}, stepFunc interface{}) { | ||||||
| 	var regex *regexp.Regexp | 	var regex *regexp.Regexp | ||||||
| 
 | 
 | ||||||
| 	switch t := expr.(type) { | 	switch t := expr.(type) { | ||||||
|  | @ -131,9 +262,21 @@ func (s *suite) Step(expr Regexp, h StepHandler) { | ||||||
| 		panic(fmt.Sprintf("expecting expr to be a *regexp.Regexp or a string, got type: %T", expr)) | 		panic(fmt.Sprintf("expecting expr to be a *regexp.Regexp or a string, got type: %T", expr)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	v := reflect.ValueOf(stepFunc) | ||||||
|  | 	typ := v.Type() | ||||||
|  | 	if typ.Kind() != reflect.Func { | ||||||
|  | 		panic(fmt.Sprintf("expected handler to be func, but got: %T", stepFunc)) | ||||||
|  | 	} | ||||||
|  | 	if typ.NumOut() != 1 { | ||||||
|  | 		panic(fmt.Sprintf("expected handler to return an error, but it has more values in return: %d", typ.NumOut())) | ||||||
|  | 	} | ||||||
|  | 	if typ.Out(0).Kind() != reflect.Interface || !typ.Out(0).Implements(errorInterface) { | ||||||
|  | 		panic(fmt.Sprintf("expected handler to return an error interface, but we have: %s", typ.Out(0).Kind())) | ||||||
|  | 	} | ||||||
| 	s.stepHandlers = append(s.stepHandlers, &StepDef{ | 	s.stepHandlers = append(s.stepHandlers, &StepDef{ | ||||||
| 		Handler: h, | 		Handler: stepFunc, | ||||||
| 		Expr:    regex, | 		Expr:    regex, | ||||||
|  | 		hv:      v, | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -262,14 +405,14 @@ func (s *suite) run() { | ||||||
| func (s *suite) matchStep(step *gherkin.Step) *StepDef { | func (s *suite) matchStep(step *gherkin.Step) *StepDef { | ||||||
| 	for _, h := range s.stepHandlers { | 	for _, h := range s.stepHandlers { | ||||||
| 		if m := h.Expr.FindStringSubmatch(step.Text); len(m) > 0 { | 		if m := h.Expr.FindStringSubmatch(step.Text); len(m) > 0 { | ||||||
| 			var args []*Arg | 			var args []interface{} | ||||||
| 			for _, a := range m[1:] { | 			for _, m := range m[1:] { | ||||||
| 				args = append(args, &Arg{value: a}) | 				args = append(args, m) | ||||||
| 			} | 			} | ||||||
| 			if step.Argument != nil { | 			if step.Argument != nil { | ||||||
| 				args = append(args, &Arg{value: step.Argument}) | 				args = append(args, step.Argument) | ||||||
| 			} | 			} | ||||||
| 			h.Args = args | 			h.args = args | ||||||
| 			return h | 			return h | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -293,7 +436,7 @@ func (s *suite) runStep(step *gherkin.Step) (err error) { | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	if err = match.Handler(match.Args...); err != nil { | 	if err = match.run(); err != nil { | ||||||
| 		s.fmt.Failed(step, match, err) | 		s.fmt.Failed(step, match, err) | ||||||
| 	} else { | 	} else { | ||||||
| 		s.fmt.Passed(step, match) | 		s.fmt.Passed(step, match) | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package godog | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/cucumber/gherkin-go" | 	"github.com/cucumber/gherkin-go" | ||||||
|  | @ -56,12 +57,12 @@ func (s *suiteContext) ResetBeforeEachScenario(interface{}) { | ||||||
| 	s.events = []*firedEvent{} | 	s.events = []*firedEvent{} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) followingStepsShouldHave(args ...*Arg) error { | func (s *suiteContext) followingStepsShouldHave(status string, steps *gherkin.DocString) error { | ||||||
| 	var expected = strings.Split(args[1].DocString().Content, "\n") | 	var expected = strings.Split(steps.Content, "\n") | ||||||
| 	var actual, unmatched []string | 	var actual, unmatched []string | ||||||
| 	var matched []int | 	var matched []int | ||||||
| 
 | 
 | ||||||
| 	switch args[0].String() { | 	switch status { | ||||||
| 	case "passed": | 	case "passed": | ||||||
| 		for _, st := range s.fmt.passed { | 		for _, st := range s.fmt.passed { | ||||||
| 			actual = append(actual, st.step.Text) | 			actual = append(actual, st.step.Text) | ||||||
|  | @ -79,11 +80,11 @@ func (s *suiteContext) followingStepsShouldHave(args ...*Arg) error { | ||||||
| 			actual = append(actual, st.step.Text) | 			actual = append(actual, st.step.Text) | ||||||
| 		} | 		} | ||||||
| 	default: | 	default: | ||||||
| 		return fmt.Errorf("unexpected step status wanted: %s", args[0].String()) | 		return fmt.Errorf("unexpected step status wanted: %s", status) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(expected) > len(actual) { | 	if len(expected) > len(actual) { | ||||||
| 		return fmt.Errorf("number of expected %s steps: %d is less than actual %s steps: %d", args[0].String(), len(expected), args[0].String(), len(actual)) | 		return fmt.Errorf("number of expected %s steps: %d is less than actual %s steps: %d", status, len(expected), status, len(actual)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, a := range actual { | 	for _, a := range actual { | ||||||
|  | @ -111,10 +112,10 @@ func (s *suiteContext) followingStepsShouldHave(args ...*Arg) error { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return fmt.Errorf("the steps: %s - is not %s", strings.Join(unmatched, ", "), args[0].String()) | 	return fmt.Errorf("the steps: %s - is not %s", strings.Join(unmatched, ", "), status) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) iAmListeningToSuiteEvents(args ...*Arg) error { | func (s *suiteContext) iAmListeningToSuiteEvents() error { | ||||||
| 	s.testedSuite.BeforeSuite(func() { | 	s.testedSuite.BeforeSuite(func() { | ||||||
| 		s.events = append(s.events, &firedEvent{"BeforeSuite", []interface{}{}}) | 		s.events = append(s.events, &firedEvent{"BeforeSuite", []interface{}{}}) | ||||||
| 	}) | 	}) | ||||||
|  | @ -136,43 +137,41 @@ func (s *suiteContext) iAmListeningToSuiteEvents(args ...*Arg) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) aFailingStep(...*Arg) error { | func (s *suiteContext) aFailingStep() error { | ||||||
| 	return fmt.Errorf("intentional failure") | 	return fmt.Errorf("intentional failure") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // parse a given feature file body as a feature | // parse a given feature file body as a feature | ||||||
| func (s *suiteContext) aFeatureFile(args ...*Arg) error { | func (s *suiteContext) aFeatureFile(name string, body *gherkin.DocString) error { | ||||||
| 	name := args[0].String() | 	ft, err := gherkin.ParseFeature(strings.NewReader(body.Content)) | ||||||
| 	body := args[1].DocString().Content |  | ||||||
| 	ft, err := gherkin.ParseFeature(strings.NewReader(body)) |  | ||||||
| 	s.testedSuite.features = append(s.testedSuite.features, &feature{Feature: ft, Path: name}) | 	s.testedSuite.features = append(s.testedSuite.features, &feature{Feature: ft, Path: name}) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) featurePath(args ...*Arg) error { | func (s *suiteContext) featurePath(path string) error { | ||||||
| 	s.testedSuite.paths = append(s.testedSuite.paths, args[0].String()) | 	s.testedSuite.paths = append(s.testedSuite.paths, path) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) parseFeatures(args ...*Arg) error { | func (s *suiteContext) parseFeatures() error { | ||||||
| 	return s.testedSuite.parseFeatures() | 	return s.testedSuite.parseFeatures() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) theSuiteShouldHave(args ...*Arg) error { | func (s *suiteContext) theSuiteShouldHave(state string) error { | ||||||
| 	if s.testedSuite.failed && args[0].String() == "passed" { | 	if s.testedSuite.failed && state == "passed" { | ||||||
| 		return fmt.Errorf("the feature suite has failed") | 		return fmt.Errorf("the feature suite has failed") | ||||||
| 	} | 	} | ||||||
| 	if !s.testedSuite.failed && args[0].String() == "failed" { | 	if !s.testedSuite.failed && state == "failed" { | ||||||
| 		return fmt.Errorf("the feature suite has passed") | 		return fmt.Errorf("the feature suite has passed") | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) iShouldHaveNumFeatureFiles(args ...*Arg) error { | func (s *suiteContext) iShouldHaveNumFeatureFiles(num int, files *gherkin.DocString) error { | ||||||
| 	if len(s.testedSuite.features) != args[0].Int() { | 	if len(s.testedSuite.features) != num { | ||||||
| 		return fmt.Errorf("expected %d features to be parsed, but have %d", args[0].Int(), len(s.testedSuite.features)) | 		return fmt.Errorf("expected %d features to be parsed, but have %d", num, len(s.testedSuite.features)) | ||||||
| 	} | 	} | ||||||
| 	expected := strings.Split(args[1].DocString().Content, "\n") | 	expected := strings.Split(files.Content, "\n") | ||||||
| 	var actual []string | 	var actual []string | ||||||
| 	for _, ft := range s.testedSuite.features { | 	for _, ft := range s.testedSuite.features { | ||||||
| 		actual = append(actual, ft.Path) | 		actual = append(actual, ft.Path) | ||||||
|  | @ -188,7 +187,7 @@ func (s *suiteContext) iShouldHaveNumFeatureFiles(args ...*Arg) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) iRunFeatureSuite(args ...*Arg) error { | func (s *suiteContext) iRunFeatureSuite() error { | ||||||
| 	if err := s.parseFeatures(); err != nil { | 	if err := s.parseFeatures(); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -196,31 +195,31 @@ func (s *suiteContext) iRunFeatureSuite(args ...*Arg) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) numScenariosRegistered(args ...*Arg) (err error) { | func (s *suiteContext) numScenariosRegistered(expected int) (err error) { | ||||||
| 	var num int | 	var num int | ||||||
| 	for _, ft := range s.testedSuite.features { | 	for _, ft := range s.testedSuite.features { | ||||||
| 		num += len(ft.ScenarioDefinitions) | 		num += len(ft.ScenarioDefinitions) | ||||||
| 	} | 	} | ||||||
| 	if num != args[0].Int() { | 	if num != expected { | ||||||
| 		err = fmt.Errorf("expected %d scenarios to be registered, but got %d", args[0].Int(), num) | 		err = fmt.Errorf("expected %d scenarios to be registered, but got %d", expected, num) | ||||||
| 	} | 	} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) thereWereNumEventsFired(args ...*Arg) error { | func (s *suiteContext) thereWereNumEventsFired(_ string, expected int, typ string) error { | ||||||
| 	var num int | 	var num int | ||||||
| 	for _, event := range s.events { | 	for _, event := range s.events { | ||||||
| 		if event.name == args[2].String() { | 		if event.name == typ { | ||||||
| 			num++ | 			num++ | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if num != args[1].Int() { | 	if num != expected { | ||||||
| 		return fmt.Errorf("expected %d %s events to be fired, but got %d", args[1].Int(), args[2].String(), num) | 		return fmt.Errorf("expected %d %s events to be fired, but got %d", expected, typ, num) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) thereWasEventTriggeredBeforeScenario(args ...*Arg) error { | func (s *suiteContext) thereWasEventTriggeredBeforeScenario(expected string) error { | ||||||
| 	var found []string | 	var found []string | ||||||
| 	for _, event := range s.events { | 	for _, event := range s.events { | ||||||
| 		if event.name != "BeforeScenario" { | 		if event.name != "BeforeScenario" { | ||||||
|  | @ -234,7 +233,7 @@ func (s *suiteContext) thereWasEventTriggeredBeforeScenario(args ...*Arg) error | ||||||
| 		case *gherkin.ScenarioOutline: | 		case *gherkin.ScenarioOutline: | ||||||
| 			name = t.Name | 			name = t.Name | ||||||
| 		} | 		} | ||||||
| 		if name == args[0].String() { | 		if name == expected { | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -245,22 +244,20 @@ func (s *suiteContext) thereWasEventTriggeredBeforeScenario(args ...*Arg) error | ||||||
| 		return fmt.Errorf("before scenario event was never triggered or listened") | 		return fmt.Errorf("before scenario event was never triggered or listened") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, args[0].String(), `"`+strings.Join(found, `", "`)+`"`) | 	return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, expected, `"`+strings.Join(found, `", "`)+`"`) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(args ...*Arg) error { | func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *gherkin.DataTable) error { | ||||||
| 	tbl := args[0].DataTable() |  | ||||||
| 	if len(tbl.Rows[0].Cells) != 2 { | 	if len(tbl.Rows[0].Cells) != 2 { | ||||||
| 		return fmt.Errorf("expected two columns for event table row, got: %d", len(tbl.Rows[0].Cells)) | 		return fmt.Errorf("expected two columns for event table row, got: %d", len(tbl.Rows[0].Cells)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, row := range tbl.Rows { | 	for _, row := range tbl.Rows { | ||||||
| 		args := []*Arg{ | 		num, err := strconv.ParseInt(row.Cells[1].Value, 10, 0) | ||||||
| 			StepArgument(""), // ignored | 		if err != nil { | ||||||
| 			StepArgument(row.Cells[1].Value), | 			return err | ||||||
| 			StepArgument(row.Cells[0].Value), |  | ||||||
| 		} | 		} | ||||||
| 		if err := s.thereWereNumEventsFired(args...); err != nil { | 		if err := s.thereWereNumEventsFired("", int(num), row.Cells[0].Value); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 gedi
						gedi