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

Watch experiment prefs #684

Merged
merged 3 commits into from Apr 22, 2017

Conversation

mythmon
Copy link
Contributor

@mythmon mythmon commented Apr 12, 2017

This is based on #674, to use the addition of the init method to PreferenceExperiments. 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.

@Osmose
Copy link
Contributor

Osmose commented Apr 12, 2017

The issue mentioned "Register the preference observer that monitors for changes to the preference." This was already done.

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)
Copy link
Contributor

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.

Copy link
Contributor

@Osmose Osmose left a 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!

@Osmose Osmose assigned mythmon and unassigned Osmose and gijsk Apr 13, 2017
@mythmon mythmon assigned Osmose and gijsk and unassigned mythmon Apr 14, 2017
@mythmon
Copy link
Contributor Author

mythmon commented Apr 14, 2017

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.

Copy link
Contributor

@Osmose Osmose left a 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 => {
Copy link
Contributor

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);
Copy link
Contributor

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");
Copy link
Contributor

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);
Copy link
Contributor

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");
Copy link
Contributor

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?

@Osmose Osmose removed their assignment Apr 14, 2017
@mythmon mythmon assigned Osmose and mythmon and unassigned Osmose, gijsk and mythmon Apr 14, 2017
Copy link
Contributor

@Osmose Osmose left a 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",
});
Copy link
Contributor

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");
Copy link
Contributor

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");
Copy link
Contributor

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.

Copy link
Contributor Author

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`);
Copy link
Contributor

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"
Copy link
Contributor

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");
Copy link
Contributor

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"),
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@Osmose Osmose assigned mythmon and unassigned Osmose Apr 14, 2017
@mythmon mythmon closed this Apr 17, 2017
@mythmon mythmon deleted the watch-experiment-prefs branch April 17, 2017 17:57
@mythmon mythmon restored the watch-experiment-prefs branch April 17, 2017 18:02
@mythmon
Copy link
Contributor Author

mythmon commented Apr 17, 2017

I accidentally closed this be deleting the branch. I'm not sure what happened, most like my automation scripts got a bit over-zealous.

@mythmon mythmon reopened this Apr 17, 2017
@mythmon mythmon force-pushed the watch-experiment-prefs branch 4 times, most recently from 57741d4 to ab79ee5 Compare April 19, 2017 22:33
@mythmon mythmon assigned Osmose and gijsk and unassigned mythmon Apr 19, 2017
…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.
Copy link
Contributor

@Osmose Osmose left a 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");
Copy link
Contributor

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() {
Copy link
Contributor

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?

Copy link
Contributor Author

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");
Copy link
Contributor

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");
Copy link
Contributor

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.

Copy link
Contributor

@gijsk gijsk left a 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. :-)

@gijsk gijsk removed their assignment Apr 20, 2017
@Osmose Osmose assigned mythmon and unassigned Osmose Apr 20, 2017
@mythmon
Copy link
Contributor Author

mythmon commented Apr 20, 2017

Removed the unneeded stuff from the tests.

Copy link
Contributor

@Osmose Osmose left a comment

Choose a reason for hiding this comment

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

@Osmose Osmose assigned gijsk and unassigned mythmon Apr 21, 2017
@gijsk gijsk assigned mythmon and unassigned gijsk Apr 22, 2017
@Osmose Osmose merged commit 44a7721 into mozilla:preference-experiment Apr 22, 2017
@mythmon mythmon deleted the watch-experiment-prefs branch May 24, 2017 22:22
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.

None yet

3 participants