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

add new option for created features with parsing from byte slices #476

Merged

Conversation

akaswenwilk
Copy link
Contributor

@akaswenwilk akaswenwilk commented May 13, 2022

Hello! In my work we are trying to build in a pre-processor for our feature files so we can have some dynamic feature generation when running our tests. I made this for us to use but though maybe it could be interesting for more people than just us. I've added another option to define custom features to be used in the suite, and then added an interface to pass in a map[string][]byte representing the path/read contents from the file (in our case, the contents after doing preprocessing). I think this implementation is maybe not fully working since it seems to be missing the line filtering, but other than that I think I've got all the other inner workings there. Just wanted to submit it if it could be interesting for anyone!

Leave me a comment if you think this is a good idea and then I can try adding some testing/additional documentation

🤔 What's changed?

⚡️ What's your motivation?

🏷️ What kind of change is this?

  • 🏦 Refactoring/debt/DX (improvement to code design, tooling, documentation etc. without changing behaviour)
  • 🐛 Bug fix (non-breaking change which fixes a defect)
  • ⚡ New feature (non-breaking change which adds new behaviour)
  • 💥 Breaking change (incompatible changes to the API)

♻️ Anything particular you want feedback on?

📋 Checklist:

  • I agree to respect and uphold the Cucumber Community Code of Conduct
  • I've changed the behaviour of the code
    • I have added/updated tests to cover my changes.
  • My change requires a change to the documentation.
    • I have updated the documentation accordingly.
  • Users should know about my change
    • I have added an entry to the "Unreleased" section of the CHANGELOG, linking to this pull request.

This text was originally generated from a template, then edited by hand. You can modify the template here.

@vearutop
Copy link
Member

This is an interesting feature and I think it can be very helpful to improve portability/embedability of tests. Let me check it in more detail to see if the API of this can be improved to be more accessible.

@mattwynne
Copy link
Member

Thanks for sharing! Can you add some tests for this @akaswenwilk?

@akaswenwilk
Copy link
Contributor Author

Thanks for sharing! Can you add some tests for this @akaswenwilk?

Sure thing!

@akaswenwilk
Copy link
Contributor Author

hi @mattwynne , so sorry for the delay to get back to this. I've added some tests for the ParseFromBytes function in the parser, but now I'm trying to address the interface a bit. Currently I've added a Features option where someone can manually pass in a slice of features, but maybe actually it makes more sense to simple add a different option instead (like FeatureContents) which would be a map[string][]byte of all the features to be run. Otherwise the user has to generate a slice of Feature models using the RetrieveFeaturesFromBytes method. Any thoughts on which might be nicer? At which point I can try adding a test in run_test.go and maybe we can finalize this? Thanks for your help!

@akaswenwilk akaswenwilk force-pushed the feature/add-ability-to-parse-from-bytes branch from ea364fd to bef3d7c Compare July 15, 2022 07:36
@codecov
Copy link

codecov bot commented Jul 15, 2022

Codecov Report

❗ No coverage uploaded for pull request base (main@b2672bb). Click here to learn what that means.
The diff coverage is 82.05%.

@@           Coverage Diff           @@
##             main     #476   +/-   ##
=======================================
  Coverage        ?   81.69%           
=======================================
  Files           ?       27           
  Lines           ?     2267           
  Branches        ?        0           
=======================================
  Hits            ?     1852           
  Misses          ?      316           
  Partials        ?       99           
Impacted Files Coverage Δ
test_context.go 77.19% <ø> (ø)
run.go 75.77% <76.92%> (ø)
internal/parser/parser.go 64.42% <84.61%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b2672bb...afa6081. Read the comment docs.

@akaswenwilk akaswenwilk force-pushed the feature/add-ability-to-parse-from-bytes branch from d3c8b15 to 25813de Compare July 23, 2022 09:03
@akaswenwilk
Copy link
Contributor Author

@vearutop, sorry for the delay, but how about this for improved API? This way the user can just pass in a map[string][]bytes directly as an option and not have to deal with trying to first parse their bytes into a feature to pass in as an option. Could be a bit simpler?

// FeatureContents allows passing in each feature manually
// where the contents of each feature is stored as a byte slice
// in a map entry
FeatureContents map[string][]byte
Copy link
Member

@vearutop vearutop Jul 24, 2022

Choose a reason for hiding this comment

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

Let's make this a slice of structs to improve clarity of data items.
Something like:

	// Features allow passing in additional features manually.
	Features []Feature
}

// Feature contains path and contents of a gherkin file.
type Feature struct {
	Path    string
	Content []byte
}

run.go Outdated
Comment on lines 230 to 234
if len(opt.FeatureContents) > 0 {
runner.features, err = parser.ParseFromBytes(opt.Tags, opt.FeatureContents)
} else {
runner.features, err = parser.ParseFeatures(opt.Tags, opt.Paths)
}
Copy link
Member

Choose a reason for hiding this comment

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

Let's change logic here so that both Paths and bytes can work together. We can check for non-empty opt.Paths and append runner.features.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm, I'm not so sure how simple this would be. From what I can see, if someone wants to run through FeatureContents only, and they leave the Paths option blank, the current behavior will force them to use a features directory by default. Either they'll have their features already in a features directory, or they won't, in which case there could be a failure due to non-existent directory, no? I think it's maybe just simpler to say, either you can use feature contents, or features, but not both. If someone is already parsing files and passing them in, I can't imagine it's much more challenging to also include from all features as opposed to from only a few. what do you think?

Copy link
Member

Choose a reason for hiding this comment

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

Hm, this is interesting point.

If someone is already parsing files and passing them in, I can't imagine it's much more challenging to also include from all features as opposed to from only a few.

Another case when this feature could be useful would be sourcing gherkin files from a remote storage (e.g. via HTTP) or generating/templating them by code. In such situation implementing additional file preloader might be a burden.

If we disable that default in case of feature contents presence it may be perceived as unexpected behavior.

	if len(opt.Paths) == 0 && len(opt.Features) == 0 {
		inf, err := os.Stat("features")
		if err == nil && inf.IsDir() {
			opt.Paths = []string{"features"}
		}
	}

If we ignore Paths (make feature contents and paths mutually exclusive) this can also be perceived as unexpected behavior (that needs to be documented or maybe even have a check and explicit failure to not mislead user).
And implementing preloader can be a minor annoyance for a user in case they want both types of features.

From these options I'm leaning to disabling the default as it seems less disruptive overall.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good!

@vearutop
Copy link
Member

@akaswenwilk overall looks good to me (apart from 2 minor suggestions that I left). 👍

@wichert
Copy link
Contributor

wichert commented Jul 25, 2022

I'm wondering if it makes sense to provide an ability to pass in an fs.FS instance, along with a wrapper to expose your map as something that implements FS.

@vearutop
Copy link
Member

I think fs.FS does not have enough info (you need to know names in advance).

// An FS provides access to a hierarchical file system.
//
// The FS interface is the minimum implementation required of the file system.
// A file system may implement additional interfaces,
// such as ReadFileFS, to provide additional or optimized functionality.
type FS interface {
	// Open opens the named file.
	//
	// When Open returns an error, it should be of type *PathError
	// with the Op field set to "open", the Path field set to name,
	// and the Err field describing the problem.
	//
	// Open should reject attempts to open names that do not satisfy
	// ValidPath(name), returning a *PathError with Err set to
	// ErrInvalid or ErrNotExist.
	Open(name string) (File, error)
}

We could use fs.ReadDirFS

// ReadDirFS is the interface implemented by a file system
// that provides an optimized implementation of ReadDir.
type ReadDirFS interface {
	FS

	// ReadDir reads the named directory
	// and returns a list of directory entries sorted by filename.
	ReadDir(name string) ([]DirEntry, error)
}

to be able to traverse entries, but I'm not sure it would be more convenient to use instead of a list of named bytes.

@akaswenwilk akaswenwilk force-pushed the feature/add-ability-to-parse-from-bytes branch from 9b38dd2 to 59a6d90 Compare July 26, 2022 10:09
@vearutop vearutop merged commit d45a9aa into cucumber:main Jul 26, 2022
@aslakhellesoy
Copy link
Contributor

Hi @akaswenwilk,

Thanks for your making your first contribution to Cucumber, and welcome to the Cucumber committers team! You can now push directly to this repo and all other repos under the cucumber organization! 🍾

In return for this generous offer we hope you will:

  • ✅ Continue to use branches and pull requests. When someone on the core team approves a pull request (yours or someone else's), you're welcome to merge it yourself.
  • 💚 Commit to setting a good example by following and upholding our code of conduct in your interactions with other collaborators and users.
  • 💬 Join the community Slack channel to meet the rest of the team and make yourself at home.
  • ℹ️ Don't feel obliged to help, just do what you can if you have the time and the energy.
  • 🙋 Ask if you need anything. We're looking for feedback about how to make the project more welcoming, so please tell us!

On behalf of the Cucumber core team,
Aslak Hellesøy
Creator of Cucumber

@akaswenwilk akaswenwilk deleted the feature/add-ability-to-parse-from-bytes branch July 26, 2022 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants