From 394b2a9d5180837ef5874f3e4ed7223fdeee576f Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Wed, 16 Dec 2015 17:56:09 +0100 Subject: [PATCH 1/2] Adds FeatureFlag FeatureFlag centralizes how we check for feature availability in a specific build --- WordPress/Classes/Utility/FeatureFlag.swift | 67 +++++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 4 ++ 2 files changed, 71 insertions(+) create mode 100644 WordPress/Classes/Utility/FeatureFlag.swift diff --git a/WordPress/Classes/Utility/FeatureFlag.swift b/WordPress/Classes/Utility/FeatureFlag.swift new file mode 100644 index 000000000000..770b51604998 --- /dev/null +++ b/WordPress/Classes/Utility/FeatureFlag.swift @@ -0,0 +1,67 @@ +/// FeatureFlag exposes a series of features to be conditionally enabled on +/// different builds. +@objc +enum FeatureFlag: Int { + /// My Sites > Site > People + /// Development on hold while we focus on Me + case People + /// Me > My Profile + /// Needs better UI for loading/failed states + case MyProfile + + /// Returns a boolean indicating if the feature is enabled + var enabled: Bool { + switch self { + case .People: + return build(.Debug) + case .MyProfile: + return build(.Debug, .Alpha, .Internal) + } + } +} + +/// Objective-C bridge for FeatureFlag. +/// +/// Since we can't expose properties on Swift enums we use a class instead +class Feature: NSObject { + /// Returns a boolean indicating if the feature is enabled + static func enabled(feature: FeatureFlag) -> Bool { + return feature.enabled + } +} + +/// Represents a build configuration. +enum Build: Int { + /// Development build, usually what you get when you run from Xcode + case Debug + /// Daily buiilds released internally for Automattic employees + case Alpha + /// Beta released internally for Automattic employees + case Internal + /// Production build released in the app store + case AppStore + + /// Returns the current build type + static var current: Build { + #if DEBUG + return .Debug + #elseif ALPHA_BUILD + return .Alpha + #elseif INTERNAL_BUILD + return .Internal + #else + return .AppStore + #endif + } +} + +/// Returns true if any of the given build types matches the current build +/// +/// Example: +/// +/// let enableExperimentalStuff = build(.Debug, .Internal) +func build(any: Build...) -> Bool { + return any.reduce(false, combine: { previous, buildValue in + previous || Build.current == buildValue + }) +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 409592bd489c..13fa9f2fb713 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -471,6 +471,7 @@ E125451812BF68F900D87A0A /* Page.m in Sources */ = {isa = PBXBuildFile; fileRef = E125451712BF68F900D87A0A /* Page.m */; }; E1266D2D1BBE8B9A00FCB6B6 /* Gravatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1266D2C1BBE8B9A00FCB6B6 /* Gravatar.swift */; }; E1266D2F1BBEC37B00FCB6B6 /* GravatarTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1266D2E1BBEC37B00FCB6B6 /* GravatarTest.swift */; }; + E12E6E331C21BA170033C5D0 /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12E6E321C21BA170033C5D0 /* FeatureFlag.swift */; }; E131CB5216CACA6B004B0314 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E131CB5116CACA6B004B0314 /* CoreText.framework */; }; E131CB5416CACB05004B0314 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E131CB5316CACB05004B0314 /* libxml2.dylib */; }; E131CB5616CACF1E004B0314 /* get-user-blogs_has-blog.json in Resources */ = {isa = PBXBuildFile; fileRef = E131CB5516CACF1E004B0314 /* get-user-blogs_has-blog.json */; }; @@ -1501,6 +1502,7 @@ E1266D2C1BBE8B9A00FCB6B6 /* Gravatar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Gravatar.swift; sourceTree = ""; }; E1266D2E1BBEC37B00FCB6B6 /* GravatarTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GravatarTest.swift; sourceTree = ""; }; E12963A8174654B2002E7744 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + E12E6E321C21BA170033C5D0 /* FeatureFlag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureFlag.swift; sourceTree = ""; }; E12F95A51557C9C20067A653 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; E12F95A61557CA210067A653 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; E12F95A71557CA400067A653 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; @@ -2773,6 +2775,7 @@ FFDA7E4F1B8DF6E500B83C56 /* BlogSiteVisibilityHelper.m */, E1E1AA8B1B7DEDFC001C8645 /* WPMapFilterReduce.h */, E1E1AA8C1B7DEDFC001C8645 /* WPMapFilterReduce.m */, + E12E6E321C21BA170033C5D0 /* FeatureFlag.swift */, ); path = Utility; sourceTree = ""; @@ -4518,6 +4521,7 @@ 594DB2951AB891A200E2E456 /* WPUserAgent.m in Sources */, C56636E91868D0CE00226AAB /* StatsViewController.m in Sources */, F128C31C1AFCC95B008C2404 /* WPMediaPickerViewController+StatusBarStyle.m in Sources */, + E12E6E331C21BA170033C5D0 /* FeatureFlag.swift in Sources */, E1B9128F1BB05B1D003C25B9 /* PeopleCell.swift in Sources */, 313AE4A019E3F20400AAFABE /* CommentViewController.m in Sources */, 93A379DB19FE6D3000415023 /* DDLogSwift.m in Sources */, From 84a1fa2339e3a4549e2e8bb73e8e434b064a1014 Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Thu, 17 Dec 2015 08:33:39 +0100 Subject: [PATCH 2/2] Adds tests for Build and FeatureFlag --- WordPress/Classes/Utility/FeatureFlag.swift | 7 ++++ WordPress/WordPress.xcodeproj/project.pbxproj | 4 ++ WordPress/WordPressTest/FeatureFlagTest.swift | 40 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 WordPress/WordPressTest/FeatureFlagTest.swift diff --git a/WordPress/Classes/Utility/FeatureFlag.swift b/WordPress/Classes/Utility/FeatureFlag.swift index 770b51604998..066b4dc542fd 100644 --- a/WordPress/Classes/Utility/FeatureFlag.swift +++ b/WordPress/Classes/Utility/FeatureFlag.swift @@ -43,6 +43,10 @@ enum Build: Int { /// Returns the current build type static var current: Build { + if let override = _overrideCurrent { + return override + } + #if DEBUG return .Debug #elseif ALPHA_BUILD @@ -53,6 +57,9 @@ enum Build: Int { return .AppStore #endif } + + /// For testing purposes only + static var _overrideCurrent: Build? = nil } /// Returns true if any of the given build types matches the current build diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 13fa9f2fb713..544e447d0b40 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -472,6 +472,7 @@ E1266D2D1BBE8B9A00FCB6B6 /* Gravatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1266D2C1BBE8B9A00FCB6B6 /* Gravatar.swift */; }; E1266D2F1BBEC37B00FCB6B6 /* GravatarTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1266D2E1BBEC37B00FCB6B6 /* GravatarTest.swift */; }; E12E6E331C21BA170033C5D0 /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12E6E321C21BA170033C5D0 /* FeatureFlag.swift */; }; + E12E6E381C21E75F0033C5D0 /* FeatureFlagTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12E6E371C21E75F0033C5D0 /* FeatureFlagTest.swift */; }; E131CB5216CACA6B004B0314 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E131CB5116CACA6B004B0314 /* CoreText.framework */; }; E131CB5416CACB05004B0314 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E131CB5316CACB05004B0314 /* libxml2.dylib */; }; E131CB5616CACF1E004B0314 /* get-user-blogs_has-blog.json in Resources */ = {isa = PBXBuildFile; fileRef = E131CB5516CACF1E004B0314 /* get-user-blogs_has-blog.json */; }; @@ -1503,6 +1504,7 @@ E1266D2E1BBEC37B00FCB6B6 /* GravatarTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GravatarTest.swift; sourceTree = ""; }; E12963A8174654B2002E7744 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; E12E6E321C21BA170033C5D0 /* FeatureFlag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureFlag.swift; sourceTree = ""; }; + E12E6E371C21E75F0033C5D0 /* FeatureFlagTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureFlagTest.swift; sourceTree = ""; }; E12F95A51557C9C20067A653 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; E12F95A61557CA210067A653 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; E12F95A71557CA400067A653 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; @@ -2667,6 +2669,7 @@ 8514B8D31AE85B19007E58BA /* WPAnalyticsTrackerMixpanelTests.m */, E19A10C91B010AA0006192B0 /* WPURLRequestTest.m */, E1E1AA8E1B7DF54B001C8645 /* WPMapFilterReduceTest.m */, + E12E6E371C21E75F0033C5D0 /* FeatureFlagTest.swift */, ); name = Utility; sourceTree = ""; @@ -4914,6 +4917,7 @@ E6B9B8AD1B94EACA0001B92F /* ReaderHelperTests.swift in Sources */, E66969CD1B9E2EBF00EC9C00 /* SafeReaderTopicToReaderTopic.m in Sources */, E66969C81B9E0A6800EC9C00 /* ReaderTopicServiceTest.swift in Sources */, + E12E6E381C21E75F0033C5D0 /* FeatureFlagTest.swift in Sources */, 931D26F519ED7E6D00114F17 /* BlogJetpackTest.m in Sources */, E1EBC3731C118ED200F638E0 /* ImmuTableTest.swift in Sources */, 93B853231B4416A30064FE72 /* WPAnalyticsTrackerAutomatticTracksTests.m in Sources */, diff --git a/WordPress/WordPressTest/FeatureFlagTest.swift b/WordPress/WordPressTest/FeatureFlagTest.swift new file mode 100644 index 000000000000..a22db5ed40af --- /dev/null +++ b/WordPress/WordPressTest/FeatureFlagTest.swift @@ -0,0 +1,40 @@ +import Nimble +import XCTest +@testable import WordPress + +class FeatureFlagTest: XCTestCase { + + func testBuild() { + Build.withCurrent(.Debug) { + expect(build(.Debug)).to(beTrue()) + expect(build(.Debug, .Alpha)).to(beTrue()) + expect(build(.Alpha)).to(beFalse()) + expect(build(.Internal)).to(beFalse()) + expect(build(.AppStore)).to(beFalse()) + } + + Build.withCurrent(.AppStore) { + expect(build(.Debug)).to(beFalse()) + expect(build(.Alpha)).to(beFalse()) + expect(build(.Internal)).to(beFalse()) + expect(build(.AppStore)).to(beTrue()) + expect(build(.Internal,.AppStore)).to(beTrue()) + } + } + + func testEnsureDisabledFeaturesInProduction() { + Build.withCurrent(.AppStore) { + expect(FeatureFlag.People.enabled).to(beFalse()) + expect(FeatureFlag.MyProfile.enabled).to(beFalse()) + } + } + +} + +extension Build { + static func withCurrent(value: Build, @noescape block: () -> Void) { + Build._overrideCurrent = value + block() + Build._overrideCurrent = nil + } +}