-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Transfer control of dev loop from file watcher to dev listener #2354
Conversation
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.
Started looking at it. Some initial feedback for some nits and questions.
I have to digest the debounce logic again - but otherwise it actually looks good to me!
@@ -26,20 +26,7 @@ import ( | |||
"github.com/GoogleContainerTools/skaffold/testutil" | |||
) | |||
|
|||
func TestWatchWithPollTrigger(t *testing.T) { |
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.
TestWatchWithPollTrigger
and TestWatchWithNotifyTrigger
should find their places in the new design too somehow. Maybe under the new trigger package?
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 added these as integration tests.
748ec32
to
eb1733d
Compare
Codecov Report
|
eb1733d
to
de22b28
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.
This is an initial feedback. I like where it's going.
pkg/skaffold/runner/dev.go
Outdated
return r.changeSet.needsReload || len(r.changeSet.needsRebuild) > 0 || r.changeSet.needsRedeploy | ||
} | ||
|
||
func (r *SkaffoldRunner) processArtifacts() error { |
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'm not sure extracting this piece of code makes it easier to read. It seems disconnected to the rest of the code now. If you still prefer to extract it, at least maybe change the name.
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.
the main reason I did this is because it's also being used in the tests, and it felt better than having duplicated code. I'll leave it moved out for now, but I'll put a TODO to try and move it into the callback registered with the monitor. I changed the name to separateBuildAndSync()
, suggestions welcome though (I don't like this one either)
pkg/skaffold/runner/dev.go
Outdated
@@ -150,5 +166,5 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la | |||
return errors.Wrap(err, "starting forwarder manager") | |||
} | |||
|
|||
return r.Watcher.Run(ctx, out, onChange) | |||
return r.Listen(ctx, out, onChange) |
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.
Could the function be called WaitForChanges
or WaitForTrigger
or something that better tels what it's doing?
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 good idea. I changed this to WatchForChanges
and the previous method with that name to LogWatchToUser
, WDYT?
@@ -42,43 +61,40 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la | |||
defer forwarderManager.Stop() | |||
|
|||
// Create watcher and register artifacts to build current state of files. | |||
changed := changes{} |
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 liked when this state was local to this function. This way, it was clear that nobody else could change its state.
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.
changeSet
is a better name though.
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 see your point, it's much easier to handle the tests when the changeSet
is embedded on the runner though. plus, it's not really true that "nobody can change its state": the file monitor does exactly this, albeit through the callbacks that we register before we start the dev loop.
if you feel strongly about it, I can change it back.
pkg/skaffold/runner/runner_test.go
Outdated
@@ -159,6 +213,38 @@ func createRunner(t *testutil.T, testBench *TestBench) *SkaffoldRunner { | |||
runner.Syncer = testBench | |||
runner.Tester = testBench | |||
runner.Deployer = testBench | |||
runner.Listener = testBench | |||
|
|||
testBench.devLoop = func() error { |
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.
It feels super strange that we have to reimplement that part of the runner. It doesn't feel like we're testing the actual code. (Maybe it wasn't the case before either)
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.
yeah, this was the case before as well, TestBench is a big mock. it is very hard to see what is the actual part of production code that is tested.
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've just checked and it looks like TestBench
is implementing Builder
, Deployer
, Tester
... but didn't implement anything from the SkaffoldRunner
. It's basically a list of mocks that record the interactions of a SkaffoldRunner
with the other components.
That doesn't seem to be the case anymore.
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.
yeah it's weird. I can't call runner.Dev()
or runner.WatchForChanges()
in the tests because those will run forever; so this was my way of "simulating" the dev loop by "mapping" out the general flow of it.
I'm not sure I totally follow what you're saying though....it sounds like you're proposing that we rewrite the TestBench
to mock out the methods defined on the SkaffoldRunner
interface? I don't totally disagree with you (I don't really like the way this test is structure to begin with) but it would be a pretty big change. what I did here was the most minimal way I found to retrofit the old tests to the new structure.
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.
What I'm saying is that runner_test.go
tries to test the code in runner.go
, so, the SkaffoldRunner. If feels weird to reimplement the devloop part in tests because then, what are we testing?
The idea of this test was to mock the Builder, the Deployer, the Tester..., group then under the name TestBench and see how SkaffoldRunner interacts with them.
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.
ok I see what you mean. I took a stab at moving the dev loop out to a method on the SkaffoldRunner
, and then calling that from the tests, with a tiny wrapper that calls the file monitor. this is the same thing the actual Listener
does whenever it receives a trigger, so it should be actually testing the correct thing now. have a look at the latest commit and tell me what you think
7c9d24c
to
bc75908
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.
LGTM with just one nit
I think it would make sense to merge just after the next release to be able to safely test the impact of those changes. |
This PR changes the way the dev loop control flow is handled in the codebase.
Previously, the file watcher was responsible for triggering the actions that
skaffold dev
would perform: when a file change was detected (whether through a polling, fsnotify, or manual mechanism), the file watcher itself was responsible for performing the builds, syncs, and deploys, and the actual "dev loop" served only as a wrapper around the file watcher.In this new design, the file watcher (renamed to File Monitor and referred to as such from now on) now only serves as a component that the dev loop uses to compute filesystem changes when necessary. The dev loop now contains a Listener, which is responsible for listening to the same trigger that was previously triggering the old file watcher. When a trigger is received, it first asks the file monitor to compute the changes on the local filesystem, and then conditionally performs the actions in the dev loop based on the changes it finds. This trigger is also moved out to a component stored on the runner, rather than being embedded in the file watcher (or in the listener).
The
changeSet
struct is moved from adev
-scoped variable to a struct on the runner, and is closed over in the callbacks that are registered to the file monitor before the listener is started. This struct still serves the same purpose: the track the "actions" that the dev loop needs to perform on each cycle. These actions are semantically computed by the file monitor when it determines the changes to the local filesystem since the last cycle of the dev loop. (This is not a new concept to this PR, just a clarification for better understanding).This is the first step toward introducing component-specific triggers in the dev loop (e.g. manual build, manual deploy, manual sync), as a way to give users more control over what parts of
skaffold dev
happen when.Summary of changes:
Watcher
->Monitor
(packagewatch
moved to packagefilemon
, and trigger-related code moved to packagetrigger
)Watcher
, nowMonitor
) from the "trigger" component that actually kicks off the dev cycle (Trigger
)changeSet
from "dev"-scoped variable to struct onRunner
: this struct is closed over in the callbacks that are registered on the file watcherRun()
method on the file monitor is now called by the dev listener whenever a trigger is receivedonChange()
callback from file watcher to dev listener: this is now called when a trigger is received by the listener, after it callsr.Monitor.Run(...)