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
Watch experiment prefs #684
Watch experiment prefs #684
Conversation
We also need to register the observer on startup, since it doesn't persist and there's a gap between when we check the value on startup and when the recipes are executed. |
const {name, branch, expired, preferenceBranchType, preferenceName, preferenceValue} = experiment; | ||
|
||
Object.values(store.data) | ||
.filter(experiment => !experiment.expired) |
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.
Firefox style would be indenting this and the next line.
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.
Looks good except for my earlier comment about registering the preference watcher on startup. Nice work!
13628fa
to
4915caf
Compare
I've made the requested changes, and re-based this on mozilla/preference-experiments, so it includes the changes from #674. This is ready for review, and if approved, ready for merge. |
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.
Looks good, just some minor changes are needed, I think. Nice work!
const {name, branch, expired, preferenceBranchType, preferenceName, preferenceValue} = experiment; | ||
Object.values(store.data) | ||
.filter(experiment => !experiment.expired) | ||
.forEach(experiment => { |
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.
You can just use PreferenceExperiments.getAllActive()
for this.
// Check that the current value of the preference is still what we set it to | ||
if (Preferences.get(experiment.preferenceName, undefined) !== experiment.preferenceValue) { | ||
// if not, stop the experiment, and skip the remaining steps | ||
this.stop(experiment.experimentName, false); |
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 think the this
keyword is broken in functions wrapped with wrapAsync
, but I have a commit lying around that fixes that: Osmose@2bf5624
|
||
// init should register an observer for experiments | ||
add_task(withMockExperiments(withMockPreferences(async function testInitRegistersObserver(experiments, mockPreferences) { | ||
dump("@@ starting test"); |
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.
Leftover debugging!
); | ||
|
||
startObserver.restore(); | ||
ok(false); |
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.
That ain't ok
}); | ||
mockPreferences.set("fake.preference", "changed value"); | ||
await PreferenceExperiments.init(); | ||
ok(experiments["test"].expired, "Experiment expires because value changed"); |
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.
Maybe we should mock stop
instead, in case its implementation changes later?
2b58c88
to
ba75614
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 understand that the dependencies here are difficult, but it would be a lot easier to review this if we split out the wrapAsync and test mochitest port into separate PRs. Up to you if you want to avoid that, though.
preferenceValue: "experiment value", | ||
expired: false, | ||
preferenceBranchType: "default", | ||
}); |
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.
Any reason why you need this extra test data?
experiments["test"] = experimentFactory({ | ||
name: "test", | ||
preferenceName: "fake.preference", | ||
preferenceValue: "experiment value", | ||
}); | ||
mockPreferences.set("fake.preference", "changed value"); | ||
await PreferenceExperiments.init(); | ||
ok(experiments["test"].expired, "Experiment expires because value changed"); | ||
ok(stopStub.calledWith("test"), "Experiment is stopped because value changed"); | ||
ok(mockPreferences.get("fake.preference"), "changed value", "Preference value was not changed"); |
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 unnecessary now that you're checking if stop was called.
experiments["test"] = experimentFactory({ | ||
name: "test", | ||
preferenceName: "fake.preference", | ||
preferenceValue: "experiment value", | ||
}); | ||
mockPreferences.set("fake.preference", "changed value"); | ||
await PreferenceExperiments.init(); | ||
ok(experiments["test"].expired, "Experiment expires because value changed"); | ||
ok(stopStub.calledWith("test"), "Experiment is stopped because value changed"); |
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 should fail because false
was also passed, which you want to assert.
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.
calledWith
only asserts about the arguments you pass. so
const foo = sinon.stub();
foo(1, 2, 3);
ok(foo.calledWith(1));
would pass. The behavior you're thinking of is a part of calledWithExact
. Which we should probably be using.
@@ -106,8 +106,9 @@ this.PreferenceExperiments = { | |||
// Check that the current value of the preference is still what we set it to | |||
if (Preferences.get(experiment.preferenceName, undefined) !== experiment.preferenceValue) { | |||
// if not, stop the experiment, and skip the remaining steps | |||
log.info(`Stopping experiment "${experiment.name}" because it's value changed`); |
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.
s/it's/its/
@@ -30,7 +28,7 @@ add_task(withMockExperiments(async function (experiments) { | |||
await PreferenceExperiments.clearAllExperimentStorage(); | |||
ok( | |||
!(await PreferenceExperiments.has("test")), | |||
"clearAllExperimentStorage removed all stored experiments", | |||
"clearAllExperimentStorage removed all stored experiments" |
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.
Don't we always want trailing commas?
@@ -399,6 +397,7 @@ add_task(withMockExperiments(withMockPreferences(async function (experiments) { | |||
// stop should remove a preference that had no value prior to an experiment for user prefs | |||
add_task(withMockExperiments(withMockPreferences(async function (experiments, mockPreferences) { | |||
const stopObserver = sinon.stub(PreferenceExperiments, "stopObserver"); | |||
mockPreferences.set("fake.preference", "defaultvalue", "default"); |
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 negates the point of this test, which is to test that preferences that have no value are remove after the test.
equal( | ||
DefaultPreferences.get("default"), | ||
is( | ||
mockPreferences.get("default", undefined, "default"), |
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 how I feel about using mockPreferences
for reading values like this. Why is it better?
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 seemed strange to deal with two different objects for interacting with preferences during tests. This way a test that uses prefs only needs one object, instead of two or three (in the case of using both default and non-default prefs). It also gives us a consistent API between setting and getting prefs, which is nice.
I accidentally closed this be deleting the branch. I'm not sure what happened, most like my automation scripts got a bit over-zealous. |
57741d4
to
ab79ee5
Compare
ab79ee5
to
3c41d82
Compare
…ass. The implementation of Preferences.jsm doesn't support watching a preference on the default branch, only on the combined set of preferences. This test cannot pass.
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 think almost all of my comments are minor test fixes that landed while this PR has been in-progress. This looks good!
One thing we might want to add are tests that init
doesn't do certain things (like setting up preference observers) for disabled prefs.
@@ -126,6 +126,7 @@ add_task(withMockExperiments(withMockPreferences(async function(experiments, moc | |||
// start should modify the user preference for the user branch type | |||
add_task(withMockExperiments(withMockPreferences(async function(experiments, mockPreferences) { | |||
const startObserver = sinon.stub(PreferenceExperiments, "startObserver"); | |||
mockPreferences.set("fake.preference", "defaultvalue", "default"); |
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.
Is this leftover from a rebase? We set the default below.
|
||
add_task(async function testHasObserver() { | ||
add_task(withMockExperiments(async function testHasObserver() { |
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.
Are all these mocks necessary?
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 mocks because they make sure that observers get properly cleaned up. We don't strictly need them on every test, but they make the tests more consistent.
@@ -331,6 +332,9 @@ add_task(withMockExperiments(async function(experiments) { | |||
// preference value. | |||
add_task(withMockExperiments(withMockPreferences(async function(experiments, mockPreferences) { | |||
const stopObserver = sinon.spy(PreferenceExperiments, "stopObserver"); | |||
const hasObserver = sinon.stub(PreferenceExperiments, "hasObserver"); |
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 don't think we need this mock anymore, as the test starts an observer itself now.
@@ -397,6 +401,7 @@ add_task(withMockExperiments(withMockPreferences(async function(experiments) { | |||
// stop should remove a preference that had no value prior to an experiment for user prefs | |||
add_task(withMockExperiments(withMockPreferences(async function(experiments, mockPreferences) { | |||
const stopObserver = sinon.stub(PreferenceExperiments, "stopObserver"); | |||
mockPreferences.set("fake.preference", "defaultvalue", "default"); |
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 shouldn't be necessary, the test asserts using isSet
now, which only checks user preferences.
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 don't have anything to add to what @Osmose already noted. :-)
Removed the unneeded stuff from the tests. |
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 based on #674, to use the addition of the
init
method toPreferenceExperiments
. The relevant commit is 13628fa.Fixes #645.
The issue mentioned "Register the preference observer that monitors for changes to the preference." This was already done.