Revision control

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
@testable import Shared
6
import XCTest
7
8
class FeatureSwitchTests: XCTestCase {
9
let buildChannel = AppConstants.BuildChannel
10
11
func testPersistent() {
12
let featureSwitch = FeatureSwitch(named: "test-persistent-over-restarts", allowPercentage: 50, buildChannel: buildChannel)
13
let prefs = MockProfilePrefs()
14
var membership = featureSwitch.isMember(prefs)
15
var changed = 0
16
for _ in 0..<100 {
17
if featureSwitch.isMember(prefs) != membership {
18
membership = !membership
19
changed += 1
20
}
21
}
22
23
XCTAssertEqual(changed, 0, "Users should get and keep the feature over restarts")
24
}
25
26
func testConsistentWhenChangingPercentage() {
27
let featureID = "test-persistent-over-releases"
28
let prefs = MockProfilePrefs()
29
let guardFeatureSwitch = FeatureSwitch(named: featureID, allowPercentage: 50, buildChannel: buildChannel)
30
while guardFeatureSwitch.alwaysMembership(prefs) {
31
guardFeatureSwitch.resetMembership(prefs)
32
}
33
var membership = false
34
var changed = 0
35
for percent in 0..<100 {
36
let featureSwitch = FeatureSwitch(named: featureID, allowPercentage: percent, buildChannel: buildChannel)
37
if featureSwitch.isMember(prefs) != membership {
38
membership = !membership
39
changed += 1
40
}
41
}
42
43
XCTAssertEqual(changed, 1, "Users should get and keep the feature if the feature is becoming successful")
44
}
45
46
func testReallyConsistentWhenChangingPercentage() {
47
for _ in 0..<1000 {
48
testConsistentWhenChangingPercentage()
49
}
50
}
51
52
func testUserEnabled() {
53
let prefs = MockProfilePrefs()
54
let featureSwitch = FeatureSwitch(named: "test-user-enabled", allowPercentage: 0, buildChannel: buildChannel)
55
XCTAssertFalse(featureSwitch.isMember(prefs), "The feature should be disabled")
56
featureSwitch.setMembership(true, for: prefs) // enable the feature
57
XCTAssertTrue(featureSwitch.isMember(prefs), "The feature should be enabled")
58
featureSwitch.setMembership(false, for: prefs) // disable the feature
59
XCTAssertFalse(featureSwitch.isMember(prefs), "The feature should be disabled again")
60
}
61
62
func testForceDisabled() {
63
let prefs = MockProfilePrefs()
64
let featureSwitch = FeatureSwitch(named: "test-user-disabled", allowPercentage: 100, buildChannel: buildChannel)
65
XCTAssertTrue(featureSwitch.isMember(prefs), "The feature should be enabled")
66
featureSwitch.setMembership(false, for: prefs) // disable the feature
67
XCTAssertFalse(featureSwitch.isMember(prefs), "The feature should be disabled again")
68
}
69
}
70
71
extension FeatureSwitchTests {
72
func test0Percent() {
73
let featureSwitch = FeatureSwitch(named: "test-never", allowPercentage: 0, buildChannel: buildChannel)
74
testExactly(featureSwitch, expected: 0)
75
testApprox(featureSwitch, expected: 0)
76
}
77
78
func test100Percent() {
79
let featureSwitch = FeatureSwitch(named: "test-always", allowPercentage: 100, buildChannel: buildChannel)
80
testExactly(featureSwitch, expected: 100)
81
testApprox(featureSwitch, expected: 100)
82
}
83
84
func test50Percent() {
85
let featureSwitch = FeatureSwitch(named: "test-half-the-population", allowPercentage: 50, buildChannel: buildChannel)
86
testApprox(featureSwitch, expected: 50)
87
}
88
89
func test30Percent() {
90
let featureSwitch = FeatureSwitch(named: "test-30%-population", allowPercentage: 30, buildChannel: buildChannel)
91
testApprox(featureSwitch, expected: 30)
92
}
93
94
func testPerformance() {
95
let featureSwitch = FeatureSwitch(named: "test-30%-population", allowPercentage: 30, buildChannel: buildChannel)
96
let prefs = MockProfilePrefs()
97
measure {
98
for _ in 0..<1000 {
99
_ = featureSwitch.isMember(prefs)
100
}
101
}
102
}
103
104
func testAppConstantsWin() {
105
// simulate in release channel, but switched off in AppConstants.
106
let featureFlaggedOff = FeatureSwitch(named: "test-release-flagged-off", false, allowPercentage: 100, buildChannel: buildChannel)
107
testExactly(featureFlaggedOff, expected: 0)
108
109
// simulate in non-release channel, but switched on in AppConstants.
110
let buildChannelAndFlaggedOn = FeatureSwitch(named: "test-flagged-on", true, allowPercentage: 0)
111
testExactly(buildChannelAndFlaggedOn, expected: 100)
112
113
// simulate in non-release channel, but switched off in AppConstants.
114
let buildChannelAndFlaggedOff = FeatureSwitch(named: "test-flagged-off", false, allowPercentage: 100)
115
testExactly(buildChannelAndFlaggedOff, expected: 0)
116
}
117
}
118
119
private extension FeatureSwitchTests {
120
func sampleN(_ featureSwitch: FeatureSwitch, testCount: Int = 1000) -> Int {
121
var count = 0
122
for _ in 0..<testCount {
123
let prefs = MockProfilePrefs()
124
if featureSwitch.isMember(prefs) {
125
count += 1
126
}
127
}
128
return count
129
}
130
131
func testExactly(_ featureSwitch: FeatureSwitch, expected: Int) {
132
let testCount = 1000
133
let count = sampleN(featureSwitch, testCount: testCount)
134
let normalizedExpectedCount = (testCount * expected) / 100
135
XCTAssertEqual(count, normalizedExpectedCount)
136
}
137
138
func testApprox(_ featureSwitch: FeatureSwitch, expected: Int, epsilon: Int = 2) {
139
let testCount = 10000
140
let count = sampleN(featureSwitch, testCount: testCount)
141
let acceptableRange = Range(uncheckedBounds: (
142
lower: testCount * (expected - epsilon) / 100,
143
upper: testCount * (expected + epsilon) / 100))
144
145
XCTAssertTrue(acceptableRange.contains(count), "\(count) in \(acceptableRange)?")
146
}
147
}