-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
build: conditional CI jobs, dynamic test requirements for release, updated independent package release process #8730
Conversation
Thanks for taking the time to open a PR!
|
Test summaryRun details
View run in Cypress Dashboard ➡️ Flakiness
This comment has been generated by cypress-bot as a result of this project's GitHub integration settings. You can manage this integration in this project's settings in the Cypress Dashboard |
scripts/check-halt.js
Outdated
} | ||
|
||
const base = await findBase(currentBranch) | ||
const changed = await changedPackages(base) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so, let me check my understanding...
if webpack-preprocessor changes, the binary will be rebuilt, because @packages/driver
depends on @cypress/webpack-preprocessor
, and that's discovered here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Also (at least for now) since almost all packages are affected by the binary, we just run everything if the binary has been changed. It might be smart to update this to actually verify that though, as some things like eslint-plugin-dev
are completely independent of the binary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also recommend reviewing the changed-packages script which was kinda squashed in without much attention in a prior PR but is pretty important here.
8433a79
to
2af4de2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish that there was a way to reuse the circle definitions. Other than that... this looks good to me.
37c722a
to
3c80f52
Compare
scripts/check-halt.js
Outdated
|
||
if (Object.keys(changed).includes(pack)) { | ||
console.log(`${pack} was directly changed, so tests run.`) | ||
runTests() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anywhere with runTests and process.exit, return the statement and rename it as per above
|
||
const versions = tags | ||
.map((tag) => (tag.match(new RegExp(`^${name}-v(.+)`)) || [])[1]) | ||
.filter((tag) => tag) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.filter((tag) => tag) | |
.filter(_.isEmpty) |
These changes update our CircleCI configuration to only run tests based on what packages have changed. These changes are intended to reduce CI run time which will reduce costs and improve iteration time as we move towards having more and more packages in the monorepo.
In addition, it improves the release process as packages can dynamically wait for dependent packages' tests to successfully finish before releasing. This adds an extra layer of security to prevent regressions from being released. All packages will be released from a single script which will wait for the correct tests to pass and release packages accordingly.
For determining what tests to run, here's the decision logic that's presented in this PR:
git merge-base develop HEAD
for change comparisondevelop
develop
is always going to be ahead ofmaster
, it works the same asgit merge-base master HEAD
for branches that were made off ofmaster
, with the added benefit that we don't have to figure out what branch it was made off of (I know I didn't explain this super well, but I've gone through just about every situation and it always chooses the correct commit to base off of)ciDependents
This commit also adds the following custom configuration options to each independent package's
package.json
:ciJobs
List of Circle CI jobs that directly test the current package. These tests must pass before the package can be released.
In addition, these tests will run when a PR is created that changes this package. All tests will run on
develop
andmaster
, regardless of what packages were changed.Note: CI jobs should be unique to a package. Any jobs that are not listed within a
ciJobs
field are considered to be part of the binary and will only run when the binary is changed.This option takes an array of CI job names.
Example
ciDependents
List of local independent (npm) packages that are dependent on the current package. The tests specified in these packages'
ciJobs
must pass before the current package will be released.When the current package is changed in a PR, it will consider these packages to be changed as well and run CI accordingly.
This option takes an array of package names.
Example
Limitations of this setup:
ciJobs
are mutually exclusive - they can only belong to one package. This can be changed easily, but since jobs not in anyciJobs
property are assumed to be part of the binary, it can be confusing if we allow multiple independent packages but can't have independent packages share a job with the binary.cypress
within their tests if they depend on it, it feels like we have decently comprehensive test coverage. Since all tests run ondevelop
, there's no way that the binary could accidentally be released if dependent package tests are failing. However, this would be a fairly simple update if needed and would require us to pick where we want to throw thepackage.json
fields for the binary (probably withincli
)Some common concerns:
scripts
or something outside of the binary lerna packages is changed?We include
scripts/*
,.node-version
,electron-builder.json
,package.json
, andyarn.lock
as part of the binary. We might need to add more files here.We already run additional tests only on develop so it makes sense that we would at least run all tests here. In addition, it's tough to come up with a good criterion for what base should be used for comparison. We could look at only what's changed in a commit and run those tests, which might make sense since we force a linear commit history and squash and merge. This has issues though since it might obscure failures and make it harder to locate where those issues were introduced. For example, take commit history
a
->b
. When commita
is added to develop, a server test fails. After that failure,b
is then added which doesn't touch the server, and "everything" passes. Now the failure froma
is obscured with the appearance of all tests passing. If we don't look at just one commit, we'd have to use the CircleCI API to look at test history or something else of that nature that greatly increases complexity for marginal benefit.How do we actually stop tests?
We use the
circleci-agent step halt
command which automatically ends a job and reports it as passing. We use the logic presented above within a job to determine if it should stop running and if so, end it before any tests actually start running.Won't this be confusing since it reports tests as passing which haven't actually run?
Yeah, it's an imperfect solution. I've added a
list-changed-packages
job so anyone can see what packages have changed for a CI run.Isn't there a better solution?
Yes, there is. This was the first approach that I considered and would allow us to actually conditionally run different workflows. However, this would require more configuration and multiple workflows - essentially we would need a "trigger" workflow to run and see what packages have changed, then call the CircleCI API to actually trigger the rest of the workflows by passing pipeline parameters. (There's no way to conditionally trigger jobs, only workflows.) This sounds like the better approach on paper, but has some issues. First, the pipeline has to run twice, once for the trigger workflow and then again for the next workflows. In addition, there's no way to share workspaces between workflows (to my knowledge). This means we'd have to run the build step for each workflow, which kinda defeats the point since the build is a pretty heavy job. We also wouldn't be able to mark jobs as required on GitHub since they might not even get started. We'd need some sort of thing like what we have with Percy to report all the statuses after everything happens. Finally, this workflow breaks completely for external PRs. We need a CircleCI API token to call the API, but we can't pass secrets to external branches for security reasons. We could (maybe, not actually sure about their permissions) move the trigger step to GitHub actions, but that's a lot. Essentially, while this solution looks better on paper it greatly increases complexity and requires significant changes to our CI system, so I propose that we just stick to the simpler solution presented in this PR.
Couple other side notes with this PR - Adds and updates some documentation around the new workflow, removes our patched
semantic-release-monorepo
since it's not necessary and wouldn't work like it was intended to.Apologies for the ugly code - probably would be a bit cleaner if I knew
lodash
better