Localisation support (#665)
* Add dialect options to support localisation * Add test for ParseFeatures support for localisation
Этот коммит содержится в:
родитель
c5a88f62c2
коммит
da4633a421
5 изменённых файлов: 162 добавлений и 23 удалений
|
@ -48,6 +48,9 @@ type Options struct {
|
||||||
// from feature files
|
// from feature files
|
||||||
Tags string
|
Tags string
|
||||||
|
|
||||||
|
// Dialect to be used to parse feature files. If not set, default to "en".
|
||||||
|
Dialect string
|
||||||
|
|
||||||
// The formatter name
|
// The formatter name
|
||||||
Format string
|
Format string
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ func ExtractFeaturePathLine(p string) (string, int) {
|
||||||
return retPath, line
|
return retPath, line
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFeatureFile(fsys fs.FS, path string, newIDFunc func() string) (*models.Feature, error) {
|
func parseFeatureFile(fsys fs.FS, path, dialect string, newIDFunc func() string) (*models.Feature, error) {
|
||||||
reader, err := fsys.Open(path)
|
reader, err := fsys.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -42,7 +42,7 @@ func parseFeatureFile(fsys fs.FS, path string, newIDFunc func() string) (*models
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc)
|
gherkinDocument, err := gherkin.ParseGherkinDocumentForLanguage(io.TeeReader(reader, &buf), dialect, newIDFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s - %v", path, err)
|
return nil, fmt.Errorf("%s - %v", path, err)
|
||||||
}
|
}
|
||||||
|
@ -54,11 +54,11 @@ func parseFeatureFile(fsys fs.FS, path string, newIDFunc func() string) (*models
|
||||||
return &f, nil
|
return &f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBytes(path string, feature []byte, newIDFunc func() string) (*models.Feature, error) {
|
func parseBytes(path string, feature []byte, dialect string, newIDFunc func() string) (*models.Feature, error) {
|
||||||
reader := bytes.NewReader(feature)
|
reader := bytes.NewReader(feature)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc)
|
gherkinDocument, err := gherkin.ParseGherkinDocumentForLanguage(io.TeeReader(reader, &buf), dialect, newIDFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s - %v", path, err)
|
return nil, fmt.Errorf("%s - %v", path, err)
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func parseBytes(path string, feature []byte, newIDFunc func() string) (*models.F
|
||||||
return &f, nil
|
return &f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFeatureDir(fsys fs.FS, dir string, newIDFunc func() string) ([]*models.Feature, error) {
|
func parseFeatureDir(fsys fs.FS, dir, dialect string, newIDFunc func() string) ([]*models.Feature, error) {
|
||||||
var features []*models.Feature
|
var features []*models.Feature
|
||||||
return features, fs.WalkDir(fsys, dir, func(p string, f fs.DirEntry, err error) error {
|
return features, fs.WalkDir(fsys, dir, func(p string, f fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -85,7 +85,7 @@ func parseFeatureDir(fsys fs.FS, dir string, newIDFunc func() string) ([]*models
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
feat, err := parseFeatureFile(fsys, p, newIDFunc)
|
feat, err := parseFeatureFile(fsys, p, dialect, newIDFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ func parseFeatureDir(fsys fs.FS, dir string, newIDFunc func() string) ([]*models
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePath(fsys fs.FS, path string, newIDFunc func() string) ([]*models.Feature, error) {
|
func parsePath(fsys fs.FS, path, dialect string, newIDFunc func() string) ([]*models.Feature, error) {
|
||||||
var features []*models.Feature
|
var features []*models.Feature
|
||||||
|
|
||||||
path, line := ExtractFeaturePathLine(path)
|
path, line := ExtractFeaturePathLine(path)
|
||||||
|
@ -114,10 +114,10 @@ func parsePath(fsys fs.FS, path string, newIDFunc func() string) ([]*models.Feat
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
return parseFeatureDir(fsys, path, newIDFunc)
|
return parseFeatureDir(fsys, path, dialect, newIDFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
ft, err := parseFeatureFile(fsys, path, newIDFunc)
|
ft, err := parseFeatureFile(fsys, path, dialect, newIDFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return features, err
|
return features, err
|
||||||
}
|
}
|
||||||
|
@ -146,14 +146,18 @@ func parsePath(fsys fs.FS, path string, newIDFunc func() string) ([]*models.Feat
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFeatures ...
|
// ParseFeatures ...
|
||||||
func ParseFeatures(fsys fs.FS, filter string, paths []string) ([]*models.Feature, error) {
|
func ParseFeatures(fsys fs.FS, filter, dialect string, paths []string) ([]*models.Feature, error) {
|
||||||
var order int
|
var order int
|
||||||
|
|
||||||
|
if dialect == "" {
|
||||||
|
dialect = gherkin.DefaultDialect
|
||||||
|
}
|
||||||
|
|
||||||
featureIdxs := make(map[string]int)
|
featureIdxs := make(map[string]int)
|
||||||
uniqueFeatureURI := make(map[string]*models.Feature)
|
uniqueFeatureURI := make(map[string]*models.Feature)
|
||||||
newIDFunc := (&messages.Incrementing{}).NewId
|
newIDFunc := (&messages.Incrementing{}).NewId
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
feats, err := parsePath(fsys, path, newIDFunc)
|
feats, err := parsePath(fsys, path, dialect, newIDFunc)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case os.IsNotExist(err):
|
case os.IsNotExist(err):
|
||||||
|
@ -189,14 +193,18 @@ func ParseFeatures(fsys fs.FS, filter string, paths []string) ([]*models.Feature
|
||||||
|
|
||||||
type FeatureContent = flags.Feature
|
type FeatureContent = flags.Feature
|
||||||
|
|
||||||
func ParseFromBytes(filter string, featuresInputs []FeatureContent) ([]*models.Feature, error) {
|
func ParseFromBytes(filter, dialect string, featuresInputs []FeatureContent) ([]*models.Feature, error) {
|
||||||
var order int
|
var order int
|
||||||
|
|
||||||
|
if dialect == "" {
|
||||||
|
dialect = gherkin.DefaultDialect
|
||||||
|
}
|
||||||
|
|
||||||
featureIdxs := make(map[string]int)
|
featureIdxs := make(map[string]int)
|
||||||
uniqueFeatureURI := make(map[string]*models.Feature)
|
uniqueFeatureURI := make(map[string]*models.Feature)
|
||||||
newIDFunc := (&messages.Incrementing{}).NewId
|
newIDFunc := (&messages.Incrementing{}).NewId
|
||||||
for _, f := range featuresInputs {
|
for _, f := range featuresInputs {
|
||||||
ft, err := parseBytes(f.Name, f.Contents, newIDFunc)
|
ft, err := parseBytes(f.Name, f.Contents, dialect, newIDFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ Feature: eat godogs
|
||||||
{Name: "MyCoolDuplicatedFeature", Contents: []byte(eatGodogContents)},
|
{Name: "MyCoolDuplicatedFeature", Contents: []byte(eatGodogContents)},
|
||||||
}
|
}
|
||||||
|
|
||||||
featureFromBytes, err := parser.ParseFromBytes("", input)
|
featureFromBytes, err := parser.ParseFromBytes("", "", input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, featureFromBytes, 1)
|
require.Len(t, featureFromBytes, 1)
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ Feature: eat godogs
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
featureFromFile, err := parser.ParseFeatures(fsys, "", []string{baseDir})
|
featureFromFile, err := parser.ParseFeatures(fsys, "", "", []string{baseDir})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, featureFromFile, 1)
|
require.Len(t, featureFromFile, 1)
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ Feature: eat godogs
|
||||||
{Name: filepath.Join(baseDir, featureFileName), Contents: []byte(eatGodogContents)},
|
{Name: filepath.Join(baseDir, featureFileName), Contents: []byte(eatGodogContents)},
|
||||||
}
|
}
|
||||||
|
|
||||||
featureFromBytes, err := parser.ParseFromBytes("", input)
|
featureFromBytes, err := parser.ParseFromBytes("", "", input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, featureFromBytes, 1)
|
require.Len(t, featureFromBytes, 1)
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
features, err := parser.ParseFeatures(test.fsys, "", test.paths)
|
features, err := parser.ParseFeatures(test.fsys, "", "", test.paths)
|
||||||
if test.expError != nil {
|
if test.expError != nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.EqualError(t, err, test.expError.Error())
|
require.EqualError(t, err, test.expError.Error())
|
||||||
|
@ -178,3 +178,132 @@ func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_ParseFeatures_Localisation(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
dialect string
|
||||||
|
contents string
|
||||||
|
}{
|
||||||
|
"english": {
|
||||||
|
dialect: "en",
|
||||||
|
contents: `
|
||||||
|
Feature: dummy
|
||||||
|
Rule: dummy
|
||||||
|
Background: dummy
|
||||||
|
Given dummy
|
||||||
|
When dummy
|
||||||
|
Then dummy
|
||||||
|
Scenario: dummy
|
||||||
|
Given dummy
|
||||||
|
When dummy
|
||||||
|
Then dummy
|
||||||
|
And dummy
|
||||||
|
But dummy
|
||||||
|
Example: dummy
|
||||||
|
Given dummy
|
||||||
|
When dummy
|
||||||
|
Then dummy
|
||||||
|
Scenario Outline: dummy
|
||||||
|
Given dummy
|
||||||
|
When dummy
|
||||||
|
Then dummy
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"afrikaans": {
|
||||||
|
dialect: "af",
|
||||||
|
contents: `
|
||||||
|
Funksie: dummy
|
||||||
|
Regel: dummy
|
||||||
|
Agtergrond: dummy
|
||||||
|
Gegewe dummy
|
||||||
|
Wanneer dummy
|
||||||
|
Dan dummy
|
||||||
|
Voorbeeld: dummy
|
||||||
|
Gegewe dummy
|
||||||
|
Wanneer dummy
|
||||||
|
Dan dummy
|
||||||
|
En dummy
|
||||||
|
Maar dummy
|
||||||
|
Voorbeelde: dummy
|
||||||
|
Gegewe dummy
|
||||||
|
Wanneer dummy
|
||||||
|
Dan dummy
|
||||||
|
Situasie Uiteensetting: dummy
|
||||||
|
Gegewe dummy
|
||||||
|
Wanneer dummy
|
||||||
|
Dan dummy
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"arabic": {
|
||||||
|
dialect: "ar",
|
||||||
|
contents: `
|
||||||
|
خاصية: dummy
|
||||||
|
Rule: dummy
|
||||||
|
الخلفية: dummy
|
||||||
|
بفرض dummy
|
||||||
|
متى dummy
|
||||||
|
اذاً dummy
|
||||||
|
مثال: dummy
|
||||||
|
بفرض dummy
|
||||||
|
متى dummy
|
||||||
|
اذاً dummy
|
||||||
|
و dummy
|
||||||
|
لكن dummy
|
||||||
|
امثلة: dummy
|
||||||
|
بفرض dummy
|
||||||
|
متى dummy
|
||||||
|
اذاً dummy
|
||||||
|
سيناريو مخطط: dummy
|
||||||
|
بفرض dummy
|
||||||
|
متى dummy
|
||||||
|
اذاً dummy
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"chinese simplified": {
|
||||||
|
dialect: "zh-CN",
|
||||||
|
contents: `
|
||||||
|
功能: dummy
|
||||||
|
规则: dummy
|
||||||
|
背景: dummy
|
||||||
|
假如 dummy
|
||||||
|
当 dummy
|
||||||
|
那么 dummy
|
||||||
|
场景: dummy
|
||||||
|
假如 dummy
|
||||||
|
当 dummy
|
||||||
|
那么 dummy
|
||||||
|
而且 dummy
|
||||||
|
但是 dummy
|
||||||
|
例子: dummy
|
||||||
|
假如 dummy
|
||||||
|
当 dummy
|
||||||
|
那么 dummy
|
||||||
|
场景大纲: dummy
|
||||||
|
假如 dummy
|
||||||
|
当 dummy
|
||||||
|
那么 dummy
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
featureFileName := "godogs.feature"
|
||||||
|
baseDir := "base"
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
fsys := fstest.MapFS{
|
||||||
|
filepath.Join(baseDir, featureFileName): {
|
||||||
|
Data: []byte(test.contents),
|
||||||
|
Mode: fs.FileMode(0o644),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
featureTestDialect, err := parser.ParseFeatures(fsys, "", test.dialect, []string{baseDir})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, featureTestDialect, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
6
run.go
6
run.go
|
@ -247,7 +247,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
|
||||||
opt.FS = storage.FS{FS: opt.FS}
|
opt.FS = storage.FS{FS: opt.FS}
|
||||||
|
|
||||||
if len(opt.FeatureContents) > 0 {
|
if len(opt.FeatureContents) > 0 {
|
||||||
features, err := parser.ParseFromBytes(opt.Tags, opt.FeatureContents)
|
features, err := parser.ParseFromBytes(opt.Tags, opt.Dialect, opt.FeatureContents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
return exitOptionError
|
return exitOptionError
|
||||||
|
@ -256,7 +256,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opt.Paths) > 0 {
|
if len(opt.Paths) > 0 {
|
||||||
features, err := parser.ParseFeatures(opt.FS, opt.Tags, opt.Paths)
|
features, err := parser.ParseFeatures(opt.FS, opt.Tags, opt.Dialect, opt.Paths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
return exitOptionError
|
return exitOptionError
|
||||||
|
@ -389,7 +389,7 @@ func (ts TestSuite) RetrieveFeatures() ([]*models.Feature, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parser.ParseFeatures(opt.FS, opt.Tags, opt.Paths)
|
return parser.ParseFeatures(opt.FS, opt.Tags, opt.Dialect, opt.Paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultOptions() (*Options, error) {
|
func getDefaultOptions() (*Options, error) {
|
||||||
|
|
|
@ -494,7 +494,7 @@ func (tc *godogFeaturesScenario) theLoggedMessagesShouldInclude(ctx context.Cont
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *godogFeaturesScenario) followingStepsShouldHave(status string, steps *DocString) error {
|
func (tc *godogFeaturesScenario) followingStepsShouldHave(status string, steps *DocString) error {
|
||||||
var expected = strings.Split(steps.Content, "\n")
|
expected := strings.Split(steps.Content, "\n")
|
||||||
var actual, unmatched, matched []string
|
var actual, unmatched, matched []string
|
||||||
|
|
||||||
storage := tc.testedSuite.storage
|
storage := tc.testedSuite.storage
|
||||||
|
@ -673,7 +673,7 @@ func (tc *godogFeaturesScenario) featurePath(path string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *godogFeaturesScenario) parseFeatures() error {
|
func (tc *godogFeaturesScenario) parseFeatures() error {
|
||||||
fts, err := parser.ParseFeatures(storage.FS{}, "", tc.paths)
|
fts, err := parser.ParseFeatures(storage.FS{}, "", "", tc.paths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1226,7 +1226,6 @@ func TestTestSuite_Run(t *testing.T) {
|
||||||
s.Step("^multistep has ambiguous$", func() Steps {
|
s.Step("^multistep has ambiguous$", func() Steps {
|
||||||
return Steps{"step is ambiguous"}
|
return Steps{"step is ambiguous"}
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
Options: &Options{
|
Options: &Options{
|
||||||
Format: "pretty",
|
Format: "pretty",
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче