├── objc ├── src │ └── XPFlagRefactoring │ │ ├── XPFlagRefactoring.exports │ │ ├── CMakeLists.txt │ │ └── README.txt ├── piranha-objc.sh ├── test.sh ├── README.md ├── generate-dylib.sh ├── generate-piranha-artifact.sh └── tests │ ├── OptimisticNamed.m.expected │ ├── OptimisticNamed.m │ ├── OptimisticNamedImpl.m.expected │ ├── OptimisticNamedOther.m.expected │ ├── OptimisticNamedOther.m │ ├── OptimisticNamedImpl.m │ ├── Control.m.expected │ ├── Treated.m.expected │ ├── Control.m │ └── Treated.m ├── report.pdf ├── java ├── gradle │ ├── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── dependencies.gradle │ └── gradle-mvn-push.gradle ├── settings.gradle ├── sample │ ├── config │ │ └── piranha.properties │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── com │ │ │ │ └── uber │ │ │ │ └── mylib │ │ │ │ ├── MyClass.expect │ │ │ │ └── MyClass.bak │ │ │ └── java │ │ │ └── com │ │ │ └── uber │ │ │ └── mylib │ │ │ └── MyClass.java │ └── build.gradle ├── piranha │ ├── config │ │ ├── fewer-piranha.properties │ │ └── piranha.properties │ ├── gradle.properties │ ├── src │ │ ├── main │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── uber │ │ │ │ └── piranha │ │ │ │ ├── PiranhaUtils.java │ │ │ │ └── UsageCounter.java │ │ └── test │ │ │ ├── resources │ │ │ └── com │ │ │ │ └── uber │ │ │ │ └── piranha │ │ │ │ ├── XPTest.java │ │ │ │ ├── XPFlagCleanerNegativeCases.java │ │ │ │ ├── XPFlagCleanerPositiveCasesControl.java │ │ │ │ ├── XPFlagCleanerPositiveCasesTreatment.java │ │ │ │ └── XPFlagCleanerPositiveCases.java │ │ │ └── java │ │ │ └── com │ │ │ └── uber │ │ │ └── piranha │ │ │ └── XPFlagCleanerTest.java │ └── build.gradle ├── config │ └── hooks │ │ ├── pre-commit-stub │ │ └── pre-commit ├── CHANGELOG.md ├── RELEASING.md ├── gradle.properties ├── build.gradle ├── gradlew.bat ├── gradlew └── README.md ├── NOTICE ├── CHANGELOG.md ├── .gitignore ├── swift ├── package.sh ├── piranha.properties ├── README.md ├── test.sh ├── src │ ├── CommandLauncher.swift │ ├── main.swift │ └── CleanupStaleFlags │ │ └── StaleFlagCleanerLauncher.swift ├── Package.swift └── tests │ ├── control.swift │ ├── treated.swift │ └── testfile.swift ├── .travis.yml ├── CONTRIBUTING.md ├── README.md ├── CODE_OF_CONDUCT.md └── LICENSE /objc/src/XPFlagRefactoring/XPFlagRefactoring.exports: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/piranha/HEAD/report.pdf -------------------------------------------------------------------------------- /java/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/piranha/HEAD/java/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | [Piranha] 2 | Copyright (c) 2018 Uber Technologies, Inc. 3 | 4 | This product includes software developed at 5 | Uber Technologies, Inc. (http://www.uber.com/). 6 | -------------------------------------------------------------------------------- /java/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | 3 | repositories { 4 | jcenter() 5 | gradlePluginPortal() 6 | } 7 | } 8 | 9 | include ':piranha' 10 | include ':sample' 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | See changelog for per-language versions of the tool: 5 | 6 | [Piranha for Java CHANGELOG.md](https://github.com/uber/piranha/tree/master/java/CHANGELOG.md) 7 | -------------------------------------------------------------------------------- /java/sample/config/piranha.properties: -------------------------------------------------------------------------------- 1 | treatedMethods=treated,flagEnabled 2 | controlMethods=flagDisabled 3 | emptyMethods=enableFlag,disableFlag 4 | annotations=FlagTesting 5 | linkURL= 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | java/.gradle 2 | java/build/ 3 | java/*/build/ 4 | .idea 5 | piranha.iml 6 | objc/process 7 | objc/piranha-objc 8 | swift/artifact 9 | swift/Piranha.xcodeproj 10 | swift/Package.resolved 11 | **/*.iml 12 | -------------------------------------------------------------------------------- /java/piranha/config/fewer-piranha.properties: -------------------------------------------------------------------------------- 1 | treatedMethods=isToggleEnabled,isFlagTreated 2 | controlMethods=isToggleDisabled 3 | emptyMethods=putToggleEnabled,putToggleDisabled,includeEvent 4 | annotations=ToggleTesting 5 | linkURL= 6 | -------------------------------------------------------------------------------- /swift/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf artifact 4 | rm -rf .build 5 | swift build -c release 6 | 7 | mkdir artifact 8 | mkdir artifact/piranha 9 | cd artifact 10 | mkdir piranha/bin 11 | cp ../.build/release/Piranha piranha/bin 12 | tar -zcvf piranha.tar.gz piranha 13 | -------------------------------------------------------------------------------- /java/piranha/config/piranha.properties: -------------------------------------------------------------------------------- 1 | treatedMethods=isToggleEnabled,isFlagTreated 2 | controlMethods=isToggleDisabled 3 | emptyMethods=putToggleEnabled,putToggleDisabled,includeEvent 4 | treatmentGroupMethods=isToggleInGroup 5 | annotations=ToggleTesting 6 | linkURL= 7 | -------------------------------------------------------------------------------- /java/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 24 14:28:21 PDT 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip 7 | -------------------------------------------------------------------------------- /swift/piranha.properties: -------------------------------------------------------------------------------- 1 | treatedMethods=isTreated(flagName) 2 | controlGroupMethods=isInControlGroup(flagName) 3 | treatmentGroupMethods=isInTreatmentGroup(groupName,flagName) 4 | testingMethods=addTreatedExperiment(flagName);removeTreatedExperiment(flagName);addExperimentParameter(flagName);experimentParameter(_,flagName) 5 | -------------------------------------------------------------------------------- /swift/README.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | - Run `swift build && swift package generate-xcodeproj` 4 | - Open Xcode project 5 | - Make required changes and do local testing 6 | - Run `sh package.sh` 7 | - Run `sh test.sh` (should print a successful message without a diff) 8 | 9 | # Usage 10 | 11 | - ` cleanup-stale-flags piranha.properties ` 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /java/config/hooks/pre-commit-stub: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # stub pre-commit hook 4 | # just a runner for the real pre-commit script 5 | # if script cannot be found, exit without error 6 | # (to not block local commits) 7 | 8 | set -e 9 | 10 | REPO_ROOT_DIR="$(git rev-parse --show-toplevel)" 11 | PRE_COMMIT_SCRIPT="${REPO_ROOT_DIR}/java/config/hooks/pre-commit" 12 | 13 | if [ -f $PRE_COMMIT_SCRIPT ]; then 14 | source $PRE_COMMIT_SCRIPT 15 | fi 16 | -------------------------------------------------------------------------------- /java/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version 0.0.3 2 | ------------- 3 | * Improve simplification of nested conditionals [#18] 4 | * Fix overlap checking for nodes with same end position [#16] 5 | * Fix broken link in README [#14] 6 | * Add auto-cleanup for java/sample integration test [#21] 7 | 8 | Version 0.0.2 9 | ------------- 10 | * Add TREATED to common group names (do not try to remove) [#12] 11 | * Piranha Java is now cut from the same repository as 12 | Piranha for Swift and Objective-C. 13 | -------------------------------------------------------------------------------- /java/config/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | REPO_ROOT_DIR="$(git rev-parse --show-toplevel)" 6 | 7 | pushd "${REPO_ROOT_DIR}/java" > /dev/null 8 | files=$((git diff --cached --name-only --diff-filter=ACMR | grep -Ei "\.java$" | sed -e 's/^java\///') || true) 9 | if [ ! -z "${files}" ]; then 10 | comma_files=$(echo "$files" | paste -s -d "," -) 11 | "${REPO_ROOT_DIR}/java/gradlew" goJF -DgoogleJavaFormat.include="$comma_files" &>/dev/null 12 | git add $(echo "$files" | paste -s -d " " -) 13 | fi 14 | popd > /dev/null 15 | -------------------------------------------------------------------------------- /objc/piranha-objc.sh: -------------------------------------------------------------------------------- 1 | 2 | #./piranha-objc.sh ObjectiveCFile.m flag_name optimistic 3 | 4 | SOURCE=$1 5 | FLAGNAME=$2 6 | FLAGTYPE=$3 7 | 8 | XcodeSDK="/Applications/Xcode.11.1.0.11A1027.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk" 9 | PIRANHA_LIB="./piranha-objc/bin/XPFlagRefactoring.dylib" 10 | 11 | ./piranha-objc/bin/clang -x objective-c \ 12 | -c $SOURCE \ 13 | -fobjc-arc \ 14 | -isysroot $XcodeSDK \ 15 | -fsyntax-only \ 16 | -Xclang -load \ 17 | -Xclang $PIRANHA_LIB \ 18 | -Xclang -plugin \ 19 | -Xclang XPFlagRefactorPlugin \ 20 | -Xclang -plugin-arg-XPFlagRefactorPlugin \ 21 | -Xclang $FLAGNAME,$FLAGTYPE 22 | -------------------------------------------------------------------------------- /java/RELEASING.md: -------------------------------------------------------------------------------- 1 | Releasing 2 | ========= 3 | 4 | 1. Change the version in `gradle.properties` to a non-SNAPSHOT version. 5 | 2. Update the `CHANGELOG.md` for the impending release. 6 | 3. Update the `README.md` with the new version. 7 | 4. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version) 8 | 5. `git tag -a vX.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version) 9 | 6. `./gradlew clean uploadArchives` 10 | 7. Update the `gradle.properties` to the next SNAPSHOT version. 11 | 8. `git commit -am "Prepare next development version."` 12 | 9. `git push && git push --tags` 13 | 10. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact. 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | 4 | - language: java 5 | install: true 6 | os: linux 7 | dist: trusty 8 | jdk: 9 | - oraclejdk8 10 | - openjdk11 11 | before_script: 12 | - cd java/ 13 | script: 14 | - ./gradlew verGJF build 15 | after_success: 16 | - ./gradlew jacocoTestReport coveralls 17 | cache: 18 | directories: 19 | - $HOME/.gradle 20 | 21 | - language: swift 22 | os: osx 23 | osx_image: xcode11.3 24 | env: SWIFT=5.1 25 | before_script: 26 | - cd swift/ 27 | script: 28 | - sh package.sh 29 | - sh test.sh 30 | 31 | notifications: 32 | email: false 33 | -------------------------------------------------------------------------------- /java/piranha/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2017. Uber Technologies 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | POM_NAME=Piranha 18 | POM_ARTIFACT_ID=piranha 19 | POM_PACKAGING=jar 20 | 21 | -------------------------------------------------------------------------------- /objc/src/XPFlagRefactoring/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # If we don't need RTTI or EH, there's no reason to export anything 2 | # from the plugin. 3 | if( NOT MSVC ) # MSVC mangles symbols differently, and 4 | # XPFlagRefactoring.export contains C++ symbols. 5 | if( NOT LLVM_REQUIRES_RTTI ) 6 | if( NOT LLVM_REQUIRES_EH ) 7 | set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/XPFlagRefactoring.exports) 8 | endif() 9 | endif() 10 | endif() 11 | 12 | add_llvm_library(XPFlagRefactoring MODULE XPFlagRefactoring.cpp PLUGIN_TOOL clang) 13 | 14 | if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN)) 15 | target_link_libraries(XPFlagRefactoring PRIVATE 16 | clangAST 17 | clangBasic 18 | clangFrontend 19 | LLVMSupport 20 | ) 21 | endif() 22 | -------------------------------------------------------------------------------- /objc/test.sh: -------------------------------------------------------------------------------- 1 | process() { 2 | SOURCE=$1 3 | FLAGNAME=$2 4 | FLAGTYPE=$3 5 | cp $SOURCE $SOURCE.bak 6 | ./piranha-objc.sh $SOURCE $FLAGNAME $FLAGTYPE > /dev/null 2> /dev/null 7 | CHANGES=$(diff -wB $SOURCE $SOURCE.expected) 8 | if [ "$CHANGES" != "" ] 9 | then 10 | echo "Expected refactoring is different from the generated refactoring using Piranha for "$SOURCE 11 | vimdiff $SOURCE $SOURCE.expected 12 | fi 13 | mv $SOURCE.bak $SOURCE 14 | 15 | } 16 | 17 | process tests/OptimisticNamed.m optimistic_stale_flag optimistic 18 | process tests/OptimisticNamedOther.m optimistic_stale_flag optimistic 19 | process tests/OptimisticNamedImpl.m optimistic_stale_flag optimistic 20 | process tests/Treated.m UBExperimentNameSomething treated 21 | process tests/Control.m UBExperimentNameSomething control 22 | 23 | echo "Tests executed completely" 24 | -------------------------------------------------------------------------------- /swift/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cleanup() { 4 | rv=$? 5 | rm $tempfile || true 6 | exit $rv 7 | } 8 | 9 | trap "cleanup" INT TERM EXIT 10 | 11 | piranha_exe=artifact/piranha/bin/Piranha 12 | 13 | tempfile=mktemp 14 | $piranha_exe cleanup-stale-flags piranha.properties tests/testfile.swift test_experiment true > "$tempfile" 15 | CHANGES=$(diff -wB $tempfile tests/treated.swift) 16 | 17 | if [ "$CHANGES" != "" ] 18 | then 19 | echo "Treatment tests failed. Differences given below:" 20 | echo "$CHANGES" 21 | exit 64 22 | fi 23 | 24 | 25 | $piranha_exe cleanup-stale-flags piranha.properties tests/testfile.swift test_experiment false > "$tempfile" 26 | CHANGES=$(diff -wB $tempfile tests/control.swift) 27 | 28 | if [ "$CHANGES" != "" ] 29 | then 30 | echo "Control tests failed. Differences given below:" 31 | echo "$CHANGES" 32 | exit 64 33 | fi 34 | 35 | echo "Tests successfully passed" 36 | 37 | -------------------------------------------------------------------------------- /objc/README.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | - To update the functionality, modify the necessary sources in the `src` directory 4 | - Run `generate-piranha-artifact.sh` (or `generate-dylib.sh`, if the `llvm` sources are already downloaded) 5 | - Run tests by executing `test.sh` and ensuring there are no changes in the refactorings 6 | 7 | # Usage 8 | - Run `piranha-objc.sh` with the appropriate parameters (file, flagname, type). 9 | -- Update `XcodeSDK` path appropriately 10 | - See the examples in `tests` directory for the original code and refactored code. 11 | 12 | # TODO 13 | - Refactoring the implementation to accept other APIs 14 | 15 | # Acknowledgements 16 | - Some aspects of the refactoring are based on this blog [article](http://www.goldsborough.me/c++/clang/llvm/tools/2017/02/24/00-00-06-emitting_diagnostics_and_fixithints_in_clang_tools/) 17 | 18 | - A majority of the test cases (as of Nov 11, 2019) are due to Nick Lauer. 19 | 20 | -------------------------------------------------------------------------------- /objc/generate-dylib.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #should be invoked after downloading the necessary llvm code 4 | 5 | set -exu 6 | 7 | PIRANHA_SRC=$(pwd)/src 8 | PIRANHA_DIR=$(pwd)/piranha-objc 9 | BASE_DIR=$(pwd) 10 | PROCESS_DIR=$(pwd)/process 11 | LLVM_PROJECT=$PROCESS_DIR/llvm-project 12 | LLVM_BUILDDIR=$LLVM_PROJECT/llvm/build 13 | DYLIB_OUTPUTDIR=$PROCESS_DIR/llvm-project/llvm/build 14 | 15 | cd $LLVM_PROJECT 16 | cp -R $BASE_DIR/src/XPFlagRefactoring/ clang/examples/XPFlagRefactoring 17 | 18 | cd $LLVM_BUILDDIR 19 | cmake -DCMAKE_BUILD_TYPE=Release .. -DLLVM_ENABLE_PROJECTS="clang" -DCLANG_BUILD_EXAMPLES="ON" 20 | 21 | make -j 12 22 | 23 | # generate the artifact 24 | cd $BASE_DIR 25 | rm -rf $PIRANHA_DIR 26 | mkdir $PIRANHA_DIR 27 | mkdir $PIRANHA_DIR/bin 28 | mkdir $PIRANHA_DIR/lib 29 | cp $DYLIB_OUTPUTDIR/lib/XPFlagRefactoring.dylib $PIRANHA_DIR/bin/ 30 | cp $DYLIB_OUTPUTDIR/bin/clang $PIRANHA_DIR/bin/ 31 | mv $DYLIB_OUTPUTDIR/lib/clang $PIRANHA_DIR/lib/ 32 | 33 | -------------------------------------------------------------------------------- /swift/src/CommandLauncher.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | enum Command: String { 20 | case cleanupStaleFlags = "cleanup-stale-flags" 21 | } 22 | 23 | protocol CommandLauncher { 24 | var command: Command { get } 25 | func launch(_ args: [String]) throws 26 | } 27 | -------------------------------------------------------------------------------- /java/piranha/src/main/java/com/uber/piranha/PiranhaUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. You may obtain a copy of the License at 6 | * 7 | *

http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | *

Unless required by applicable law or agreed to in writing, software distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.uber.piranha; 15 | 16 | public class PiranhaUtils { 17 | public static final String DELETE_REQUEST_COMMENT = 18 | "//[PIRANHA_DELETE_FILE_SEQ] Delete this class.\n"; 19 | 20 | public static final String HELPER_CLASS = "// [PIRANHA_STALE_PLUGIN_HELPER_CLASS]"; 21 | } 22 | -------------------------------------------------------------------------------- /objc/src/XPFlagRefactoring/README.txt: -------------------------------------------------------------------------------- 1 | This is a simple example demonstrating how to use clang's facility for 2 | providing AST consumers using a plugin. 3 | 4 | Build the plugin by running `make` in this directory. 5 | 6 | Once the plugin is built, you can run it using: 7 | -- 8 | Linux: 9 | $ clang -cc1 -load ../../Debug+Asserts/lib/libPrintFunctionNames.so -plugin print-fns some-input-file.c 10 | $ clang -cc1 -load ../../Debug+Asserts/lib/libPrintFunctionNames.so -plugin print-fns -plugin-arg-print-fns help -plugin-arg-print-fns --example-argument some-input-file.c 11 | $ clang -cc1 -load ../../Debug+Asserts/lib/libPrintFunctionNames.so -plugin print-fns -plugin-arg-print-fns -an-error some-input-file.c 12 | 13 | Mac: 14 | $ clang -cc1 -load ../../Debug+Asserts/lib/libPrintFunctionNames.dylib -plugin print-fns some-input-file.c 15 | $ clang -cc1 -load ../../Debug+Asserts/lib/libPrintFunctionNames.dylib -plugin print-fns -plugin-arg-print-fns help -plugin-arg-print-fns --example-argument some-input-file.c 16 | $ clang -cc1 -load ../../Debug+Asserts/lib/libPrintFunctionNames.dylib -plugin print-fns -plugin-arg-print-fns -an-error some-input-file.c 17 | -------------------------------------------------------------------------------- /java/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | GROUP=com.uber.piranha 16 | VERSION_NAME=0.0.4-SNAPSHOT 17 | 18 | POM_DESCRIPTION=A refactoring tool for cleaning up stale feature flag code in Java 19 | 20 | POM_URL=https://github.com/uber/Piranha/ 21 | POM_SCM_URL=https://github.com/uber/Piranha/ 22 | POM_SCM_CONNECTION=scm:git:git://github.com/uber/Piranha.git 23 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/uber/Piranha.git 24 | 25 | POM_LICENCE_NAME=The MIT License 26 | POM_LICENCE_URL=https://opensource.org/licenses/MIT 27 | POM_LICENCE_DIST=repo 28 | 29 | POM_DEVELOPER_ID=murali 30 | POM_DEVELOPER_NAME=Murali Krishna Ramanathan 31 | 32 | -------------------------------------------------------------------------------- /java/sample/src/main/resources/com/uber/mylib/MyClass.expect: -------------------------------------------------------------------------------- 1 | package com.uber.mylib; 2 | 3 | // DO *NOT* MODIFY THIS FILE INSIDE main/java/src/... 4 | 5 | // This file is copied automatically from main/resources/com/uber/mylib/MyClass.bak before and after 6 | // every build, as part of the Piranha integration tests (the idea is not to leave the "cleaned" 7 | // file lying around after Piranha runs, as that pollutes the git staging area). 8 | // If you need to change this file, change the copy inside main/resources/com/uber/mylib/MyClass.bak 9 | 10 | // After clean-up, this file must match main/resources/com/uber/mylib/MyClass.expect 11 | 12 | /** A sample class. */ 13 | public class MyClass { 14 | 15 | enum TestExperimentName { 16 | 17 | } 18 | 19 | private XPTest expt; 20 | 21 | public void foo() { 22 | System.out.println("Hello World"); 23 | } 24 | 25 | public void bar() { 26 | 27 | } 28 | 29 | static class XPTest { 30 | public boolean flagEnabled(TestExperimentName x) { 31 | return true; 32 | } 33 | 34 | public boolean enableFlag(TestExperimentName x) { 35 | return true; 36 | } 37 | 38 | public boolean disableFlag(TestExperimentName x) { 39 | return true; 40 | } 41 | 42 | public boolean flagDisabled(TestExperimentName x) { 43 | return true; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /java/piranha/src/test/resources/com/uber/piranha/XPTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. You may obtain a copy of the License at 6 | * 7 | *

http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | *

Unless required by applicable law or agreed to in writing, software distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.uber.piranha; 15 | 16 | class XPTest { 17 | public boolean isToggleEnabled(Object x) { 18 | return true; 19 | } 20 | 21 | public boolean putToggleEnabled(Object x) { 22 | return true; 23 | } 24 | 25 | public boolean includeEvent(Object x) { 26 | return true; 27 | } 28 | 29 | public boolean isToggleDisabled(Object x) { 30 | return true; 31 | } 32 | 33 | public boolean putToggleDisabled(Object x) { 34 | return true; 35 | } 36 | 37 | public boolean isFlagTreated(Object x) { 38 | return true; 39 | } 40 | 41 | public boolean isToggleInGroup(Object x, Object y) { 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /objc/generate-piranha-artifact.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exu 4 | 5 | BRANCH_SCHEME=${BRANCH_SCHEME:-"swift-5.1-branch"} 6 | PIRANHA_VERSION="${PIRANHA_VERSION:-"0.0.5"}" 7 | PIRANHA_SRC=$(pwd)/src 8 | PIRANHA_DIR=$(pwd)/piranha-objc 9 | BASE_DIR=$(pwd) 10 | PROCESS_DIR=$(pwd)/process 11 | LLVM_PROJECT=$PROCESS_DIR/llvm-project 12 | LLVM_BUILDDIR=$LLVM_PROJECT/llvm/build 13 | DYLIB_OUTPUTDIR=$PROCESS_DIR/llvm-project/llvm/build 14 | 15 | mkdir $PROCESS_DIR 16 | mkdir $LLVM_PROJECT 17 | cd $PROCESS_DIR 18 | 19 | # set up the source for building the clang plugin 20 | git clone https://github.com/apple/swift.git 21 | ./swift/utils/update-checkout --clone --scheme $BRANCH_SCHEME 22 | 23 | cd $LLVM_PROJECT 24 | mkdir clang/examples/XPFlagRefactoring 25 | cp -R $BASE_DIR/src/XPFlagRefactoring/ clang/examples/XPFlagRefactoring 26 | echo "add_subdirectory(XPFlagRefactoring)" >> clang/examples/CMakeLists.txt 27 | 28 | # build the plugin 29 | mkdir $LLVM_BUILDDIR 30 | cd $LLVM_BUILDDIR 31 | cmake -DCMAKE_BUILD_TYPE=Release .. -DLLVM_ENABLE_PROJECTS="clang" -DCLANG_BUILD_EXAMPLES="ON" 32 | 33 | make -j 12 34 | 35 | # generate the artifact 36 | cd $BASE_DIR 37 | rm -rf $PIRANHA_DIR 38 | mkdir $PIRANHA_DIR 39 | mkdir $PIRANHA_DIR/bin 40 | mkdir $PIRANHA_DIR/lib 41 | cp $DYLIB_OUTPUTDIR/lib/XPFlagRefactoring.dylib $PIRANHA_DIR/bin/ 42 | cp $DYLIB_OUTPUTDIR/bin/clang $PIRANHA_DIR/bin/ 43 | mv $DYLIB_OUTPUTDIR/lib/clang $PIRANHA_DIR/lib/ 44 | -------------------------------------------------------------------------------- /swift/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | /** 4 | * Copyright (c) 2019 Uber Technologies, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | 20 | import PackageDescription 21 | 22 | let package = Package( 23 | name: "Piranha", 24 | platforms: [ 25 | .macOS(.v10_14), 26 | ], 27 | dependencies: [ 28 | .package(url: "https://github.com/apple/swift-syntax.git", .exact("0.50100.0")), 29 | ], 30 | targets: [ 31 | .target( 32 | name: "Piranha", 33 | dependencies: ["SwiftSyntax"], 34 | path: "src"), 35 | .testTarget( 36 | name: "PiranhaTests", 37 | dependencies: ["Piranha"], 38 | path: "tests"), 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /java/sample/src/main/java/com/uber/mylib/MyClass.java: -------------------------------------------------------------------------------- 1 | package com.uber.mylib; 2 | 3 | // DO *NOT* MODIFY THIS FILE INSIDE main/java/src/... 4 | 5 | // This file is copied automatically from main/resources/com/uber/mylib/MyClass.bak before and after 6 | // every build, as part of the Piranha integration tests (the idea is not to leave the "cleaned" 7 | // file lying around after Piranha runs, as that pollutes the git staging area). 8 | // If you need to change this file, change the copy inside main/resources/com/uber/mylib/MyClass.bak 9 | 10 | // After clean-up, this file must match main/resources/com/uber/mylib/MyClass.expect 11 | 12 | /** A sample class. */ 13 | public class MyClass { 14 | 15 | enum TestExperimentName { 16 | SAMPLE_STALE_FLAG 17 | } 18 | 19 | private XPTest expt; 20 | 21 | public void foo() { 22 | if (expt.flagEnabled(TestExperimentName.SAMPLE_STALE_FLAG)) { 23 | System.out.println("Hello World"); 24 | } 25 | } 26 | 27 | public void bar() { 28 | if (expt.flagDisabled(TestExperimentName.SAMPLE_STALE_FLAG)) { 29 | System.out.println("Hi World"); 30 | } 31 | } 32 | 33 | static class XPTest { 34 | public boolean flagEnabled(TestExperimentName x) { 35 | return true; 36 | } 37 | 38 | public boolean enableFlag(TestExperimentName x) { 39 | return true; 40 | } 41 | 42 | public boolean disableFlag(TestExperimentName x) { 43 | return true; 44 | } 45 | 46 | public boolean flagDisabled(TestExperimentName x) { 47 | return true; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /java/sample/src/main/resources/com/uber/mylib/MyClass.bak: -------------------------------------------------------------------------------- 1 | package com.uber.mylib; 2 | 3 | // DO *NOT* MODIFY THIS FILE INSIDE main/java/src/... 4 | 5 | // This file is copied automatically from main/resources/com/uber/mylib/MyClass.bak before and after 6 | // every build, as part of the Piranha integration tests (the idea is not to leave the "cleaned" 7 | // file lying around after Piranha runs, as that pollutes the git staging area). 8 | // If you need to change this file, change the copy inside main/resources/com/uber/mylib/MyClass.bak 9 | 10 | // After clean-up, this file must match main/resources/com/uber/mylib/MyClass.expect 11 | 12 | /** A sample class. */ 13 | public class MyClass { 14 | 15 | enum TestExperimentName { 16 | SAMPLE_STALE_FLAG 17 | } 18 | 19 | private XPTest expt; 20 | 21 | public void foo() { 22 | if (expt.flagEnabled(TestExperimentName.SAMPLE_STALE_FLAG)) { 23 | System.out.println("Hello World"); 24 | } 25 | } 26 | 27 | public void bar() { 28 | if (expt.flagDisabled(TestExperimentName.SAMPLE_STALE_FLAG)) { 29 | System.out.println("Hi World"); 30 | } 31 | } 32 | 33 | static class XPTest { 34 | public boolean flagEnabled(TestExperimentName x) { 35 | return true; 36 | } 37 | 38 | public boolean enableFlag(TestExperimentName x) { 39 | return true; 40 | } 41 | 42 | public boolean disableFlag(TestExperimentName x) { 43 | return true; 44 | } 45 | 46 | public boolean flagDisabled(TestExperimentName x) { 47 | return true; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /objc/tests/OptimisticNamed.m.expected: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | 21 | @protocol UBAdvancedExperimenting 22 | 23 | - (BOOL)optimisticFeatureFlagEnabledForExperiment:(NSString *)experimentKey; 24 | 25 | @end 26 | 27 | #define UBOptimisticNamedFeatureFlagIsEnabled(flagName) ([UBOptimisticFeatureFlag isEnabled:@ #flagName]) 28 | 29 | #define UBOptimisticNamedFeatureFlag(flagName) \ 30 | if (UBOptimisticNamedFeatureFlagIsEnabled(flagName)) 31 | 32 | 33 | @interface UBOptimisticFeatureFlag : NSObject 34 | @property (class, nonatomic) id advancedExperiments; 35 | + (BOOL)isEnabled:(NSString *)flagName; 36 | @end 37 | 38 | @implementation UBOptimisticFeatureFlag 39 | 40 | + (BOOL)isEnabled:(NSString *)flagName 41 | { 42 | } 43 | 44 | @end 45 | 46 | 47 | @implementation OptimisticTest 48 | 49 | - (void)optimisticFeatureFlag_macro { 50 | NSLog(@"1"); 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /swift/src/main.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import SwiftSyntax 19 | 20 | // Create a CommandLauncher and add it here if you want to add a new Piranha command to the Swift executable. 21 | let commandLaunchers: [CommandLauncher] = [ 22 | StaleFlagCleanerLauncher(), 23 | ] 24 | 25 | private func showError(_ text: String) { 26 | print("ERROR: \(text)") 27 | } 28 | 29 | guard CommandLine.arguments.count > 1 else { 30 | showError("Command must be specified.") 31 | exit(-1) 32 | } 33 | let command = CommandLine.arguments[1] 34 | 35 | let filteredCommandLaunchers = commandLaunchers.filter { (commandLauncher: CommandLauncher) -> Bool in 36 | return commandLauncher.command.rawValue == command 37 | } 38 | 39 | guard let firstCommandLauncher = filteredCommandLaunchers.first else { 40 | showError("Unhandled command.") 41 | exit(-1) 42 | } 43 | 44 | do { 45 | try firstCommandLauncher.launch(CommandLine.arguments) 46 | } catch { 47 | showError("Failed to launch command.") 48 | } 49 | -------------------------------------------------------------------------------- /java/gradle/dependencies.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2018. Uber Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | def versions = [ 18 | errorProne : "2.3.2", 19 | ] 20 | 21 | def apt = [ 22 | autoService : "com.google.auto.service:auto-service:1.0-rc3", 23 | ] 24 | 25 | def build = [ 26 | errorProneCheckApi : "com.google.errorprone:error_prone_check_api:${versions.errorProne}", 27 | errorProneCore : "com.google.errorprone:error_prone_core:${versions.errorProne}", 28 | errorProneJavac : "com.google.errorprone:javac:9+181-r4173-1", 29 | errorProneTestHelpers : "com.google.errorprone:error_prone_test_helpers:${versions.errorProne}", 30 | gradleErrorPronePlugin : "net.ltgt.gradle:gradle-errorprone-plugin:0.0.16", 31 | jsr305Annotations : "com.google.code.findbugs:jsr305:3.0.2", 32 | ] 33 | 34 | def test = [ 35 | junit4 : "junit:junit:4.12", 36 | daggerAnnotations : "com.google.dagger:dagger:2.16", 37 | ] 38 | 39 | ext.deps = [ 40 | "apt": apt, 41 | "build": build, 42 | "test": test, 43 | "versions": versions 44 | ] 45 | -------------------------------------------------------------------------------- /objc/tests/OptimisticNamed.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | 21 | @protocol UBAdvancedExperimenting 22 | 23 | - (BOOL)optimisticFeatureFlagEnabledForExperiment:(NSString *)experimentKey; 24 | 25 | @end 26 | 27 | #define UBOptimisticNamedFeatureFlagIsEnabled(flagName) ([UBOptimisticFeatureFlag isEnabled:@ #flagName]) 28 | 29 | #define UBOptimisticNamedFeatureFlag(flagName) \ 30 | if (UBOptimisticNamedFeatureFlagIsEnabled(flagName)) 31 | 32 | 33 | @interface UBOptimisticFeatureFlag : NSObject 34 | @property (class, nonatomic) id advancedExperiments; 35 | + (BOOL)isEnabled:(NSString *)flagName; 36 | @end 37 | 38 | @implementation UBOptimisticFeatureFlag 39 | 40 | + (BOOL)isEnabled:(NSString *)flagName 41 | { 42 | } 43 | 44 | @end 45 | 46 | 47 | @implementation OptimisticTest 48 | 49 | - (void)optimisticFeatureFlag_macro { 50 | UBOptimisticNamedFeatureFlag(optimistic_stale_flag) { 51 | NSLog(@"1"); 52 | } 53 | else { 54 | NSLog(@"2"); 55 | } 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /java/piranha/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2018. Uber Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import net.ltgt.gradle.errorprone.CheckSeverity 18 | 19 | plugins { 20 | id "java" 21 | // For code coverage: 22 | id 'jacoco' 23 | } 24 | 25 | sourceCompatibility = "1.8" 26 | targetCompatibility = "1.8" 27 | 28 | dependencies { 29 | compileOnly deps.apt.autoService 30 | annotationProcessor deps.apt.autoService 31 | 32 | compileOnly deps.build.errorProneCheckApi 33 | 34 | testCompile deps.test.junit4 35 | testCompile deps.test.daggerAnnotations 36 | testCompile(deps.build.errorProneTestHelpers) { 37 | exclude group: "junit", module: "junit" 38 | } 39 | } 40 | 41 | javadoc { 42 | failOnError = false 43 | } 44 | 45 | test { 46 | maxHeapSize = "1024m" 47 | jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" 48 | } 49 | 50 | apply from: rootProject.file("gradle/gradle-mvn-push.gradle") 51 | 52 | // From https://github.com/kt3k/coveralls-gradle-plugin 53 | jacocoTestReport { 54 | reports { 55 | xml.enabled = true // coveralls plugin depends on xml format report 56 | html.enabled = true 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | Contributing to Piranha 3 | ======================= 4 | 5 | Uber welcomes contributions of all kinds and sizes. This includes everything from from simple bug reports to large features. 6 | 7 | Workflow 8 | -------- 9 | 10 | We love GitHub issues! 11 | 12 | For small feature requests, an issue first proposing it for discussion or demo implementation in a PR suffice. 13 | 14 | For big features, please open an issue so that we can agree on the direction, and hopefully avoid investing a lot of time on a feature that might need reworking. 15 | 16 | Small pull requests for things like typos, bug fixes, etc are always welcome. 17 | 18 | DOs and DON'Ts 19 | -------------- 20 | 21 | * DO format your code using Google Java Format (for Java), clang-format (for ObjC). 22 | * DO include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken. 23 | * DO keep the discussions focused. When a new or related topic comes up it's often better to create new issue than to side track the discussion. 24 | 25 | * DON'T submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it. 26 | 27 | Guiding Principles 28 | ------------------ 29 | 30 | * We allow anyone to participate in our projects. Tasks can be carried out by anyone that demonstrates the capability to complete them 31 | * Always be respectful of one another. Assume the best in others and act with empathy at all times 32 | * Collaborate closely with individuals maintaining the project or experienced users. Getting ideas out in the open and seeing a proposal before it's a pull request helps reduce redundancy and ensures we're all connected to the decision making process 33 | 34 | -------------------------------------------------------------------------------- /objc/tests/OptimisticNamedImpl.m.expected: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | 21 | @protocol UBAdvancedExperimenting 22 | 23 | - (BOOL)optimisticFeatureFlagEnabledForExperiment:(NSString *)experimentKey; 24 | 25 | @end 26 | 27 | #define UBOptimisticNamedFeatureFlagIsEnabled(flagName) ([UBOptimisticFeatureFlag isEnabled:@ #flagName]) 28 | 29 | #define UBOptimisticNamedFeatureFlag(flagName) \ 30 | if (UBOptimisticNamedFeatureFlagIsEnabled(flagName)) 31 | 32 | 33 | @interface UBOptimisticFeatureFlag : NSObject 34 | @property (class, nonatomic) id advancedExperiments; 35 | + (BOOL)isEnabled:(NSString *)flagName; 36 | @end 37 | 38 | @implementation UBOptimisticFeatureFlag 39 | 40 | + (BOOL)isEnabled:(NSString *)flagName 41 | { 42 | } 43 | 44 | @end 45 | 46 | 47 | @interface UBEOptimisticFeatureFlagCache : NSObject 48 | 49 | 50 | @end 51 | 52 | @implementation UBEOptimisticFeatureFlagCache 53 | 54 | 55 | 56 | @implementation OptimisticTest 57 | 58 | - (void)optimisticFeatureFlag_macro { 59 | NSLog(@"1"); 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /objc/tests/OptimisticNamedOther.m.expected: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | 21 | @protocol UBAdvancedExperimenting 22 | 23 | - (BOOL)optimisticFeatureFlagEnabledForExperiment:(NSString *)experimentKey; 24 | 25 | @end 26 | 27 | @interface UBDependencyGraph : NSObject 28 | 29 | + (UBDependencyGraph *)currentGraph; 30 | 31 | - (nullable id)implementationForProtocol:(Protocol *)protocol; 32 | 33 | @end 34 | 35 | /** 36 | See UBOptimisticNamedFeatureFlag. This is a helper macro. 37 | */ 38 | #define UBOptimisticNamedFeatureFlagIsEnabled(flagName) \ 39 | ([[[UBDependencyGraph currentGraph] \ 40 | implementationForProtocol:@protocol(UBAdvancedExperimenting)] \ 41 | optimisticFeatureFlagEnabledForExperiment: \ 42 | [NSString stringWithFormat:@"ios_%@_wide_optimistic_rollback", \ 43 | [@ #flagName lowercaseString]]]) 44 | 45 | #define UBOptimisticNamedFeatureFlag(flagName) \ 46 | if (UBOptimisticNamedFeatureFlagIsEnabled(flagName)) 47 | 48 | 49 | @implementation OptimisticTest 50 | 51 | - (void)optimisticFeatureFlag_macro { 52 | NSLog(@"1"); 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /java/sample/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017. Uber Technologies 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import net.ltgt.gradle.errorprone.CheckSeverity 18 | 19 | plugins { 20 | id "java" 21 | } 22 | 23 | sourceCompatibility = "1.8" 24 | targetCompatibility = "1.8" 25 | 26 | 27 | dependencies { 28 | annotationProcessor project(path: ":piranha") 29 | } 30 | 31 | tasks.withType(JavaCompile) { 32 | options.errorprone { 33 | check("Piranha", CheckSeverity.WARN) 34 | } 35 | options.errorprone.errorproneArgs << "-XepPatchChecks:Piranha" 36 | options.errorprone.errorproneArgs << "-XepPatchLocation:IN_PLACE" 37 | options.errorprone.errorproneArgs << "-XepOpt:Piranha:FlagName=SAMPLE_STALE_FLAG" 38 | options.errorprone.errorproneArgs << "-XepOpt:Piranha:IsTreated=true" 39 | options.errorprone.errorproneArgs << "-XepOpt:Piranha:Config=config/piranha.properties" 40 | } 41 | 42 | compileJava.doFirst { 43 | exec { 44 | commandLine 'cp', 'src/main/resources/com/uber/mylib/MyClass.bak', 'src/main/java/com/uber/mylib/MyClass.java' 45 | } 46 | } 47 | compileJava.doLast { 48 | exec { 49 | commandLine 'diff', 'src/main/java/com/uber/mylib/MyClass.java', 'src/main/resources/com/uber/mylib/MyClass.expect' 50 | } 51 | exec { 52 | commandLine 'cp', 'src/main/resources/com/uber/mylib/MyClass.bak', 'src/main/java/com/uber/mylib/MyClass.java' 53 | } 54 | } -------------------------------------------------------------------------------- /objc/tests/OptimisticNamedOther.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | 21 | @protocol UBAdvancedExperimenting 22 | 23 | - (BOOL)optimisticFeatureFlagEnabledForExperiment:(NSString *)experimentKey; 24 | 25 | @end 26 | 27 | @interface UBDependencyGraph : NSObject 28 | 29 | + (UBDependencyGraph *)currentGraph; 30 | 31 | - (nullable id)implementationForProtocol:(Protocol *)protocol; 32 | 33 | @end 34 | 35 | /** 36 | See UBOptimisticNamedFeatureFlag. This is a helper macro. 37 | */ 38 | #define UBOptimisticNamedFeatureFlagIsEnabled(flagName) \ 39 | ([[[UBDependencyGraph currentGraph] \ 40 | implementationForProtocol:@protocol(UBAdvancedExperimenting)] \ 41 | optimisticFeatureFlagEnabledForExperiment: \ 42 | [NSString stringWithFormat:@"ios_%@_wide_optimistic_rollback", \ 43 | [@ #flagName lowercaseString]]]) 44 | 45 | #define UBOptimisticNamedFeatureFlag(flagName) \ 46 | if (UBOptimisticNamedFeatureFlagIsEnabled(flagName)) 47 | 48 | 49 | @implementation OptimisticTest 50 | 51 | - (void)optimisticFeatureFlag_macro { 52 | UBOptimisticNamedFeatureFlag(optimistic_stale_flag) { 53 | NSLog(@"1"); 54 | } 55 | else { 56 | NSLog(@"2"); 57 | } 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /objc/tests/OptimisticNamedImpl.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | 21 | @protocol UBAdvancedExperimenting 22 | 23 | - (BOOL)optimisticFeatureFlagEnabledForExperiment:(NSString *)experimentKey; 24 | 25 | @end 26 | 27 | 28 | #define UBOptimisticNamedFeatureFlagIsEnabled(flagName) ([UBOptimisticFeatureFlag isEnabled:@ #flagName]) 29 | 30 | #define UBOptimisticNamedFeatureFlag(flagName) \ 31 | if (UBOptimisticNamedFeatureFlagIsEnabled(flagName)) 32 | 33 | 34 | @interface UBOptimisticFeatureFlag : NSObject 35 | @property (class, nonatomic) id advancedExperiments; 36 | + (BOOL)isEnabled:(NSString *)flagName; 37 | @end 38 | 39 | @implementation UBOptimisticFeatureFlag 40 | 41 | + (BOOL)isEnabled:(NSString *)flagName 42 | { 43 | } 44 | 45 | @end 46 | 47 | 48 | @interface UBEOptimisticFeatureFlagCache : NSObject 49 | 50 | + (BOOL)optimistic_stale_flag; 51 | 52 | @end 53 | 54 | @implementation UBEOptimisticFeatureFlagCache 55 | 56 | + (BOOL)optimistic_stale_flag { 57 | static BOOL enabled = NO; 58 | static dispatch_once_t onceToken; 59 | dispatch_once(&onceToken, ^{ 60 | UBOptimisticNamedFeatureFlag(optimistic_stale_flag) 61 | { 62 | enabled = YES; 63 | } 64 | }); 65 | return enabled; 66 | } 67 | 68 | 69 | @implementation OptimisticTest 70 | 71 | - (void)optimisticFeatureFlag_macro { 72 | if (UBEOptimisticFeatureFlagCache.optimistic_stale_flag) { 73 | NSLog(@"1"); 74 | } else { 75 | NSLog(@"2"); 76 | } 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2018. Uber Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import net.ltgt.gradle.errorprone.CheckSeverity 18 | 19 | plugins { 20 | id "com.github.sherter.google-java-format" version "0.7.1" 21 | id "net.ltgt.errorprone" version "0.6" apply false 22 | } 23 | 24 | repositories { 25 | // to get the google-java-format jar and dependencies 26 | jcenter() 27 | } 28 | 29 | apply from: "gradle/dependencies.gradle" 30 | 31 | subprojects { project -> 32 | project.apply plugin: "net.ltgt.errorprone" 33 | project.dependencies { 34 | errorprone deps.build.errorProneCore 35 | errorproneJavac deps.build.errorProneJavac 36 | } 37 | project.tasks.withType(JavaCompile) { 38 | dependsOn(installGitHooks) 39 | options.compilerArgs += [ 40 | "-Xlint:unchecked", 41 | "-Xlint:rawtypes" 42 | ] 43 | options.errorprone { 44 | // disable warnings in generated code; AutoValue code fails UnnecessaryParentheses check 45 | disableWarningsInGeneratedCode = true 46 | // this check is too noisy 47 | check("StringSplitter", CheckSeverity.OFF) 48 | check("WildcardImport", CheckSeverity.ERROR) 49 | } 50 | } 51 | 52 | repositories { 53 | jcenter() 54 | } 55 | 56 | } 57 | 58 | googleJavaFormat { 59 | toolVersion = "1.6" 60 | } 61 | 62 | //////////////////////////////////////////////////////////////////////// 63 | // 64 | // Google Java Format pre-commit hook installation 65 | // 66 | 67 | tasks.register('installGitHooks', Copy) { 68 | from(file('config/hooks/pre-commit-stub')) { 69 | rename 'pre-commit-stub', 'pre-commit' 70 | } 71 | into file('../.git/hooks') 72 | fileMode 0777 73 | } 74 | -------------------------------------------------------------------------------- /swift/src/CleanupStaleFlags/StaleFlagCleanerLauncher.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import SwiftSyntax 19 | 20 | // groupname is optional. the other arguments are necessary 21 | class StaleFlagCleanerLauncher: CommandLauncher { 22 | let command: Command = .cleanupStaleFlags 23 | 24 | func launch(_ args: [String]) throws { 25 | var sourceFile = URL(fileURLWithPath: "") 26 | var configFile = URL(fileURLWithPath: "") 27 | var flagName = "" 28 | var groupName = "" 29 | var isTreated = false 30 | 31 | for (index, argument) in args.enumerated() { 32 | switch index { 33 | case 2: 34 | configFile = URL(fileURLWithPath: argument) 35 | case 3: 36 | sourceFile = URL(fileURLWithPath: argument) 37 | case 4: 38 | flagName = argument 39 | case 5: 40 | isTreated = argument.elementsEqual("true") ? true : false 41 | case 6: 42 | groupName = argument 43 | default: break 44 | } 45 | } 46 | 47 | guard flagName.count > 0 else { 48 | // swiftlint:disable:next custom_rules 49 | print("Flag name is necessary to use the refactoring tool.") 50 | exit(-1) 51 | } 52 | 53 | let parsed = try SyntaxParser.parse(sourceFile) 54 | let cleaner = XPFlagCleaner(with: configFile, flag: flagName, behavior: isTreated, group: groupName) 55 | 56 | // swiftlint:disable:next custom_rules 57 | var refactoredOutput = cleaner.visit(parsed) 58 | if cleaner.deepClean() { 59 | cleaner.setNextPass() 60 | refactoredOutput = cleaner.visit(parsed) 61 | } 62 | 63 | // swiftlint:disable:next custom_rules 64 | print(refactoredOutput, terminator: "") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Piranha 2 | 3 | [![Join the chat at https://gitter.im/uber/piranha](https://badges.gitter.im/uber/piranha.svg)](https://gitter.im/uber/piranha?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Feature flags are commonly used to enable gradual rollout or experiment with new features. In a few cases, even after the purpose of the flag is accomplished, the code pertaining to the feature flag is not removed. We refer to such flags as stale flags. The presence of code pertaining to stale flags can have the following drawbacks: 6 | - Unnecessary code clutter increases the overall complexity w.r.t maintenance resulting in reduced developer productivity 7 | - The flags can interfere with other experimental flags (e.g., due to nesting under a flag that is always false) 8 | - Presence of unused code in the source as well as the binary 9 | - Stale flags can also cause bugs 10 | 11 | Piranha is a tool to automatically refactor code related to stale flags. At a higher level, the input to the tool is the name of the flag and the expected behavior, after specifying a list of APIs related to flags in a properties file. Piranha will use these inputs to automatically refactor the code according to the expected behavior. 12 | 13 | This repository contains three independent versions of Piranha, one for each of the three supported languages: Objective-C, Swift, and Java. 14 | 15 | To use/build each version, look under the corresponding [lang]/ directory and follow instructions in the corresponding [lang]/README.md file. Make sure to cd into that directory to build any related code following the instructions in the README. 16 | 17 | - [PiranhaJava](java/README.md) 18 | - [PiranhaObjC](objc/README.md) 19 | - [PiranhaSwift](swift/README.md) 20 | 21 | A few additional links on Piranha: 22 | 23 | - A technical [report](report.pdf) detailing our experiences with using Piranha at Uber. 24 | - A [blogpost](https://eng.uber.com/piranha/) presenting more information on Piranha. 25 | - 6 minute [video](https://www.youtube.com/watch?v=V5XirDs6LX8&feature=emb_logo) overview of Piranha. 26 | 27 | ## Support 28 | 29 | Please feel free to [open a GitHub issue](https://github.com/uber/piranha/issues) if you have any questions on how to use Piranha. 30 | 31 | ## Contributors 32 | 33 | We'd love for you to contribute to Piranha! Please note that once 34 | you create a pull request, you will be asked to sign our [Uber Contributor License Agreement](https://cla-assistant.io/uber/piranha). 35 | 36 | ## License 37 | Piranha is licensed under the Apache 2.0 license. See the LICENSE file for more information. 38 | -------------------------------------------------------------------------------- /java/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 7 | 8 | ## Our Standards 9 | 10 | Examples of behavior that contributes to creating a positive environment include: 11 | 12 | * Using welcoming and inclusive language 13 | * Being respectful of differing viewpoints and experiences 14 | * Gracefully accepting constructive criticism 15 | * Focusing on what is best for the community 16 | * Showing empathy towards other community members 17 | 18 | Examples of unacceptable behavior by participants include: 19 | 20 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 21 | * Trolling, insulting/derogatory comments, and personal or political attacks 22 | * Public or private harassment 23 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 24 | * Other conduct which could reasonably be considered inappropriate in a professional setting 25 | 26 | ## Our Responsibilities 27 | 28 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 29 | 30 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 31 | 32 | ## Scope 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 35 | 36 | ## Enforcement 37 | 38 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mobile-open-source@uber.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 39 | 40 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 41 | 42 | ## Attribution 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 45 | 46 | [homepage]: http://contributor-covenant.org 47 | [version]: http://contributor-covenant.org/version/1/4/ 48 | -------------------------------------------------------------------------------- /java/piranha/src/test/resources/com/uber/piranha/XPFlagCleanerNegativeCases.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. You may obtain a copy of the License at 6 | * 7 | *

http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | *

Unless required by applicable law or agreed to in writing, software distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.uber.piranha; 15 | 16 | import java.lang.annotation.ElementType; 17 | import java.lang.annotation.Retention; 18 | import java.lang.annotation.RetentionPolicy; 19 | import java.lang.annotation.Target; 20 | 21 | class XPFlagCleanerNegativeCases { 22 | 23 | enum TestExperimentName { 24 | RANDOM_FLAG 25 | } 26 | 27 | @Retention(RetentionPolicy.RUNTIME) 28 | @Target({ElementType.METHOD}) 29 | @interface ToggleTesting { 30 | TestExperimentName[] treated(); 31 | } 32 | 33 | private XPTest experimentation; 34 | 35 | private boolean tBool = false; 36 | 37 | private boolean tBool1 = false; 38 | private boolean tBool2 = false; 39 | 40 | public void conditional_contains_nonstale_flag() { 41 | if (experimentation.isFlagTreated(TestExperimentName.RANDOM_FLAG)) { 42 | System.out.println("Hello World"); 43 | } 44 | } 45 | 46 | public void conditional_with_else_contains_nonstale_flag() { 47 | if (experimentation.isToggleEnabled(TestExperimentName.RANDOM_FLAG)) { 48 | System.out.println("Hello World"); 49 | } else { 50 | System.out.println("Hi world"); 51 | } 52 | } 53 | 54 | public void complex_conditional_contains_nonstale_flag() { 55 | if (tBool1 || (tBool2 && experimentation.isToggleEnabled(TestExperimentName.RANDOM_FLAG))) { 56 | System.out.println("Hello World"); 57 | } else { 58 | System.out.println("Hi world"); 59 | } 60 | } 61 | 62 | public void assignments_containing_nonstale_flag() { 63 | tBool = experimentation.isToggleEnabled(TestExperimentName.RANDOM_FLAG); 64 | 65 | tBool = experimentation.isToggleEnabled(TestExperimentName.RANDOM_FLAG) && tBool; 66 | 67 | tBool = experimentation.isToggleEnabled(TestExperimentName.RANDOM_FLAG) || tBool; 68 | 69 | tBool = experimentation.isToggleEnabled(TestExperimentName.RANDOM_FLAG) && (tBool1 || tBool2); 70 | } 71 | 72 | public boolean return_contains_nonstale_flag() { 73 | return experimentation.isToggleEnabled(TestExperimentName.RANDOM_FLAG); 74 | } 75 | 76 | public void condexp_contains_nonstale_flag() { 77 | tBool = experimentation.isToggleEnabled(TestExperimentName.RANDOM_FLAG) ? true : false; 78 | } 79 | 80 | public void misc_xp_apis_containing_nonstale_flag() { 81 | if (experimentation.isToggleEnabled(TestExperimentName.RANDOM_FLAG) && (tBool1 || tBool2)) {} 82 | 83 | experimentation.putToggleEnabled(TestExperimentName.RANDOM_FLAG); 84 | 85 | experimentation.putToggleDisabled(TestExperimentName.RANDOM_FLAG); 86 | 87 | if (experimentation.isToggledDisabled(TestExperimentName.RANDOM_FLAG) && (tBool1 || tBool2)) {} 88 | } 89 | 90 | @ToggleTesting(treated = TestExperimentName.RANDOM_FLAG) 91 | public void annotation_test() {} 92 | 93 | class XPTest { 94 | public boolean isToggleEnabled(TestExperimentName x) { 95 | return true; 96 | } 97 | 98 | public boolean putToggleEnabled(TestExperimentName x) { 99 | return true; 100 | } 101 | 102 | public boolean isToggledDisabled(TestExperimentName x) { 103 | return true; 104 | } 105 | 106 | public boolean isFlagTreated(TestExperimentName x) { 107 | return true; 108 | } 109 | 110 | public boolean putToggleDisabled(TestExperimentName x) { 111 | return true; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /java/piranha/src/test/resources/com/uber/piranha/XPFlagCleanerPositiveCasesControl.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. You may obtain a copy of the License at 6 | * 7 | *

http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | *

Unless required by applicable law or agreed to in writing, software distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.uber.piranha; 15 | 16 | import dagger.Provides; 17 | import java.lang.annotation.ElementType; 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | import java.lang.annotation.Target; 21 | import javax.inject.Inject; 22 | 23 | class XPFlagCleanerPositiveCases { 24 | 25 | enum TestExperimentName { 26 | // BUG: Diagnostic contains: Cleans stale XP flags 27 | } 28 | 29 | enum AnotherTestExperimentName { 30 | // BUG: Diagnostic contains: Cleans stale XP flags 31 | } 32 | 33 | @Retention(RetentionPolicy.RUNTIME) 34 | public @interface Autorollout { 35 | boolean staged() default false; 36 | } 37 | 38 | @Retention(RetentionPolicy.RUNTIME) 39 | @Target({ElementType.METHOD}) 40 | @interface ToggleTesting { 41 | TestExperimentName[] treated(); 42 | } 43 | 44 | private XPTest experimentation; 45 | 46 | private boolean tBool = false; 47 | 48 | public void conditional_contains_stale_flag() {} 49 | 50 | public void conditional_with_else_contains_stale_flag() { 51 | System.out.println("Hi world"); 52 | } 53 | 54 | public void complex_conditional_contains_stale_flag() { 55 | System.out.println("Hello World"); 56 | } 57 | 58 | public void other_api_stale_flag() { 59 | System.out.println("Hi world"); 60 | } 61 | 62 | public void assignments_containing_stale_flag() { 63 | tBool = false; 64 | 65 | tBool = false; 66 | 67 | tBool = true; 68 | 69 | tBool = tBool; 70 | 71 | tBool = false; 72 | } 73 | 74 | public boolean return_contains_stale_flag() { 75 | return false; 76 | } 77 | 78 | public void condexp_contains_stale_flag() { 79 | tBool = false; 80 | } 81 | 82 | public void misc_xp_apis_containing_stale_flag() {} 83 | 84 | public int return_within_if_basic() { 85 | // BUG: Diagnostic contains: Cleans stale XP flags 86 | return 30; 87 | } 88 | 89 | public int return_within_if_additional(int x) { 90 | if (x == 0) { 91 | return 75; 92 | } 93 | if (x == 1) return 76; 94 | if (x == 2) { 95 | int y = 3; 96 | return y + 10; 97 | } 98 | if (x == 3) { 99 | int z = 4; 100 | z = z * 5; 101 | return z + 10; 102 | } 103 | return 100; 104 | } 105 | 106 | private int testRemovingInjectField() { 107 | return 2; 108 | } 109 | 110 | @Inject XPTest injectedExperimentsMultipleUses; 111 | 112 | private void randomSet(XPTest x) { 113 | injectedExperimentsMultipleUses = x; 114 | } 115 | 116 | private int testNotRemovingInjectField() { 117 | // BUG: Diagnostic contains: Cleans stale XP flags 118 | return 2; 119 | } 120 | 121 | // BUG: Diagnostic contains: Cleans stale XP flags 122 | @Provides 123 | public int unusedParamTestWithDeletion() { 124 | return 2; 125 | } 126 | 127 | @Provides 128 | public int unusedParamTestWithoutDeletion(XPTest x) { 129 | 130 | if (x != null) { 131 | // just another use to prevent deletion of this parameter. 132 | } 133 | 134 | return 2; 135 | } 136 | 137 | // Based off D2516909 138 | private void testMultipleCalls(int x) { 139 | if (x > 0) { 140 | // comment1 141 | return; 142 | } 143 | 144 | // do something here 145 | return; 146 | } 147 | 148 | public int or_compounded_with_not(int x, boolean extra_toggle) { 149 | if (extra_toggle) { 150 | return 0; 151 | } else { 152 | return 1; 153 | } 154 | } 155 | 156 | public int remove_else_if(boolean extra_toggle) { 157 | if (extra_toggle) { 158 | return 0; 159 | } else { 160 | return 1; 161 | } 162 | } 163 | 164 | class XPTest { 165 | public boolean isToggleEnabled(TestExperimentName x) { 166 | return true; 167 | } 168 | 169 | public boolean putToggleEnabled(TestExperimentName x) { 170 | return true; 171 | } 172 | 173 | public boolean includeEvent(TestExperimentName x) { 174 | return true; 175 | } 176 | 177 | public boolean isToggleDisabled(TestExperimentName x) { 178 | return true; 179 | } 180 | 181 | public boolean putToggleDisabled(TestExperimentName x) { 182 | return true; 183 | } 184 | 185 | public boolean isFlagTreated(TestExperimentName x) { 186 | return true; 187 | } 188 | 189 | public boolean isToggleInGroup(TestExperimentName x) { 190 | return true; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /java/piranha/src/test/resources/com/uber/piranha/XPFlagCleanerPositiveCasesTreatment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. You may obtain a copy of the License at 6 | * 7 | *

http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | *

Unless required by applicable law or agreed to in writing, software distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.uber.piranha; 15 | 16 | import dagger.Provides; 17 | import java.lang.annotation.ElementType; 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | import java.lang.annotation.Target; 21 | import javax.inject.Inject; 22 | 23 | class XPFlagCleanerPositiveCases { 24 | 25 | enum TestExperimentName { 26 | // BUG: Diagnostic contains: Cleans stale XP flags 27 | } 28 | 29 | enum AnotherTestExperimentName { 30 | // BUG: Diagnostic contains: Cleans stale XP flags 31 | } 32 | 33 | @Retention(RetentionPolicy.RUNTIME) 34 | public @interface Autorollout { 35 | boolean staged() default false; 36 | } 37 | 38 | @Retention(RetentionPolicy.RUNTIME) 39 | @Target({ElementType.METHOD}) 40 | @interface ToggleTesting { 41 | TestExperimentName[] treated(); 42 | } 43 | 44 | private XPTest experimentation; 45 | 46 | private boolean tBool = false; 47 | 48 | public void conditional_contains_stale_flag() { 49 | System.out.println("Hello World"); 50 | } 51 | 52 | public void conditional_with_else_contains_stale_flag() { 53 | System.out.println("Hello World"); 54 | } 55 | 56 | public void complex_conditional_contains_stale_flag() { 57 | System.out.println("Hello World"); 58 | } 59 | 60 | public void other_api_stale_flag() { 61 | System.out.println("Hello World"); 62 | } 63 | 64 | public void assignments_containing_stale_flag() { 65 | tBool = true; 66 | 67 | tBool = true; 68 | 69 | tBool = true; 70 | 71 | tBool = true; 72 | 73 | tBool = true; 74 | } 75 | 76 | public boolean return_contains_stale_flag() { 77 | return true; 78 | } 79 | 80 | public void condexp_contains_stale_flag() { 81 | tBool = true; 82 | } 83 | 84 | public void misc_xp_apis_containing_stale_flag() {} 85 | 86 | public int return_within_if_basic() { 87 | // BUG: Diagnostic contains: Cleans stale XP flags 88 | return 20; 89 | } 90 | 91 | public int return_within_if_additional(int x) { 92 | if (x == 0) { 93 | return 0; 94 | } 95 | if (x == 1) return 1; 96 | if (x == 2) { 97 | int y = 3; 98 | y++; 99 | return y; 100 | } 101 | 102 | if (x == 3) { 103 | int z = 4; 104 | z++; 105 | return z; 106 | } 107 | return 100; 108 | } 109 | 110 | // BUG: Diagnostic contains: Cleans stale XP flags 111 | public void annotation_test() {} 112 | 113 | private int testRemovingInjectField() { 114 | return 1; 115 | } 116 | 117 | @Inject XPTest injectedExperimentsMultipleUses; 118 | 119 | private void randomSet(XPTest x) { 120 | injectedExperimentsMultipleUses = x; 121 | } 122 | 123 | private int testNotRemovingInjectField() { 124 | // BUG: Diagnostic contains: Cleans stale XP flags 125 | return 1; 126 | } 127 | 128 | // BUG: Diagnostic contains: Cleans stale XP flags 129 | @Provides 130 | public int unusedParamTestWithDeletion() { 131 | // BUG: Diagnostic contains: Cleans stale XP flags 132 | return 1; 133 | } 134 | 135 | @Provides 136 | public int unusedParamTestWithoutDeletion(XPTest x) { 137 | if (x != null) { 138 | // just another use to prevent deletion of this parameter. 139 | } 140 | 141 | // BUG: Diagnostic contains: Cleans stale XP flags 142 | return 1; 143 | } 144 | 145 | // Based off D2516909 146 | private void testMultipleCalls(int x) { 147 | if (x > 0) { 148 | // comment0 149 | return; 150 | } 151 | 152 | // do something here 153 | return; 154 | } 155 | 156 | public int or_compounded_with_not(int x, boolean extra_toggle) { 157 | return 0; 158 | } 159 | 160 | public int remove_else_if(boolean extra_toggle) { 161 | if (extra_toggle) { 162 | return 0; 163 | } else { 164 | return 2; 165 | } 166 | } 167 | 168 | class XPTest { 169 | public boolean isToggleEnabled(TestExperimentName x) { 170 | return true; 171 | } 172 | 173 | public boolean putToggleEnabled(TestExperimentName x) { 174 | return true; 175 | } 176 | 177 | public boolean includeEvent(TestExperimentName x) { 178 | return true; 179 | } 180 | 181 | public boolean isToggleDisabled(TestExperimentName x) { 182 | return true; 183 | } 184 | 185 | public boolean putToggleDisabled(TestExperimentName x) { 186 | return true; 187 | } 188 | 189 | public boolean isFlagTreated(TestExperimentName x) { 190 | return true; 191 | } 192 | 193 | public boolean isToggleInGroup(TestExperimentName x) { 194 | return true; 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /java/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /objc/tests/Control.m.expected: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | #define UBEEXPERIMENT_NAMES_APP \ 21 | X(UBExperimentNameSomething, @"some_random_experiment") 22 | 23 | #define X(name, value) extern NSString *const name; 24 | UBEEXPERIMENT_NAMES_APP 25 | #undef X 26 | 27 | @interface Test : NSObject 28 | 29 | - (id)andReturn:(id)anObject; 30 | - (id)andReturnValue:(NSValue *)aValue; 31 | 32 | @property (nonatomic, readonly) Test *(^ _andReturn)(id); 33 | 34 | @end 35 | 36 | @protocol UBCachedExperimenting 37 | 38 | - (BOOL)isValidExperiment:(NSString *)experimentKey; 39 | 40 | - (BOOL)isInControlGroupForExperiment:(NSString *)experimentKey; 41 | 42 | - (BOOL)isTreatedForExperiment:(NSString *)experimentKey; 43 | 44 | - (BOOL)isInTreatmentGroup:(NSString *)treatmentGroupKey 45 | forExperiment:(NSString *)experimentKey; 46 | 47 | - (nullable NSString *)stringParameter:(NSString *)parameterName 48 | forExperiment:(NSString *)experimentKey 49 | withDefaultValue:(nullable NSString *)defaultValue; 50 | 51 | - (nullable NSNumber *)numberParameter:(NSString *)parameterName 52 | forExperiment:(NSString *)experimentKey 53 | withDefaultValue:(nullable NSNumber *)defaultValue; 54 | 55 | - (void)sendInclusionEventForExperiment:(NSString *)experimentKey; 56 | 57 | - (void)sendInclusionEventForExperiment:(NSString *)experimentKey 58 | forTreatmentGroup:(NSString *)treatmentGroupKey; 59 | 60 | @end 61 | 62 | 63 | @interface UBEExperimentExamples : NSObject 64 | 65 | @property(nonatomic, readonly) id experiments; 66 | @property(nonatomic, readonly) id experimentsMock; 67 | 68 | @end 69 | 70 | @implementation UBEExperimentExamples 71 | 72 | - (void)featureFlag_treated { 73 | 74 | 75 | NSLog(@"2"); 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | NSLog(@"6"); 84 | 85 | NSLog(@"8"); 86 | 87 | NSLog(@"9"); 88 | 89 | NSLog(@"10"); 90 | 91 | 92 | 93 | NSLog(@"12"); 94 | 95 | int x; 96 | NSLog(@"13"); 97 | 98 | 99 | 100 | } 101 | 102 | - (void)featureFlag_control { 103 | 104 | } 105 | 106 | // Group names are TODO 107 | - (void)featureFlag_specificGroup { 108 | if ([self.experiments 109 | isInTreatmentGroup:@"groupname" 110 | forExperiment:UBExperimentNameSomething]) { 111 | NSLog(@"treated for groupname treatment group"); 112 | } 113 | } 114 | 115 | // Non boolean returns are TODO 116 | - (void)featureFlag_stringParameter { 117 | NSString *string = [self.experiments 118 | stringParameter:@"param_name" 119 | forExperiment:UBExperimentNameSomething 120 | withDefaultValue:@"default"]; 121 | NSLog(@"%@", string); 122 | } 123 | 124 | - (void)featureFlag_numberParameter { 125 | NSNumber *number = [self.experiments 126 | numberParameter:@"param_name" 127 | forExperiment:UBExperimentNameSomething 128 | withDefaultValue:@2]; 129 | NSLog(@"%@", number); 130 | } 131 | 132 | - (void)featureFlag_inclusions { 133 | 134 | 135 | } 136 | 137 | #pragma mark - Tests 138 | 139 | 140 | - (void)featureFlag_test { 141 | 142 | 143 | [self setNumberParameter:@"" value:@2 forExperimentNamed:@""]; 144 | [self enableExperimentNamed:@"experiment" 145 | forTreatmentGroup:@"treatmentGroup"]; 146 | } 147 | 148 | 149 | - (void)featureFlag_variants { 150 | [self testAllFeatureFlagVariants:^(NSArray *enabledFlags) { 151 | } 152 | featureFlagNames:@[ 153 | 154 | ]]; 155 | } 156 | 157 | - (void)featureFlag_snapshotVariants { 158 | [self snapshotTestAllFeatureFlagVariants:^(NSArray *enabledFlags, 159 | NSString *identifier) { 160 | // test code 161 | } 162 | featureFlagNames:@[ 163 | 164 | ]]; 165 | } 166 | 167 | #pragma mark - helpers 168 | 169 | // Enable a feature flag for all treatment groups. Cleared at the end of every 170 | // test 171 | - (void)enableFeatureFlagNamed:(NSString *)name { 172 | return; 173 | } 174 | 175 | // Enable an experiment for one treatment group. Cleared at the end of every 176 | // test 177 | - (void)enableExperimentNamed:(NSString *)name 178 | forTreatmentGroup:(NSString *)treatmentGroup { 179 | return; 180 | } 181 | 182 | // Enable an experiment for one treatment group. Cleared at the end of every 183 | // test 184 | - (void)setNumberParameter:(NSString *)parameterName 185 | value:(NSNumber *)number 186 | forExperimentNamed:(NSString *)name { 187 | return; 188 | } 189 | 190 | /** Puts experiment in control */ 191 | - (void)disableExperimentNamed:(NSString *)name { 192 | return; 193 | } 194 | 195 | typedef void (^FeatureFlagTestBlock)(NSArray *enabledFlags); 196 | typedef void (^SnapshotTestBlock)(NSArray *enabledFlags, 197 | NSString *identifier); 198 | 199 | // Runs the test block with all possible combinations of experiments enabled and 200 | // disabled. 201 | - (void)testAllFeatureFlagVariants:(FeatureFlagTestBlock)testBlock 202 | featureFlagNames:(NSArray *)featureFlagNames { 203 | return; 204 | } 205 | 206 | // Runs the test block with all possible combinations of experiments enabled and 207 | // disabled. Passes in an identifier for the snapshot 208 | - (void)snapshotTestAllFeatureFlagVariants:(SnapshotTestBlock)testBlock 209 | featureFlagNames: 210 | (NSArray *)featureFlagNames { 211 | return; 212 | } 213 | 214 | @end 215 | -------------------------------------------------------------------------------- /objc/tests/Treated.m.expected: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | #define UBEEXPERIMENT_NAMES_APP \ 21 | X(UBExperimentNameSomething, @"some_random_experiment") 22 | 23 | #define X(name, value) extern NSString *const name; 24 | UBEEXPERIMENT_NAMES_APP 25 | #undef X 26 | 27 | @interface Test : NSObject 28 | 29 | - (id)andReturn:(id)anObject; 30 | - (id)andReturnValue:(NSValue *)aValue; 31 | 32 | @property (nonatomic, readonly) Test *(^ _andReturn)(id); 33 | 34 | @end 35 | 36 | @protocol UBCachedExperimenting 37 | 38 | - (BOOL)isValidExperiment:(NSString *)experimentKey; 39 | 40 | - (BOOL)isInControlGroupForExperiment:(NSString *)experimentKey; 41 | 42 | - (BOOL)isTreatedForExperiment:(NSString *)experimentKey; 43 | 44 | - (BOOL)isInTreatmentGroup:(NSString *)treatmentGroupKey 45 | forExperiment:(NSString *)experimentKey; 46 | 47 | - (nullable NSString *)stringParameter:(NSString *)parameterName 48 | forExperiment:(NSString *)experimentKey 49 | withDefaultValue:(nullable NSString *)defaultValue; 50 | 51 | - (nullable NSNumber *)numberParameter:(NSString *)parameterName 52 | forExperiment:(NSString *)experimentKey 53 | withDefaultValue:(nullable NSNumber *)defaultValue; 54 | 55 | - (void)sendInclusionEventForExperiment:(NSString *)experimentKey; 56 | 57 | - (void)sendInclusionEventForExperiment:(NSString *)experimentKey 58 | forTreatmentGroup:(NSString *)treatmentGroupKey; 59 | 60 | @end 61 | 62 | 63 | @interface UBEExperimentExamples : NSObject 64 | 65 | @property(nonatomic, readonly) id experiments; 66 | @property(nonatomic, readonly) id experimentsMock; 67 | 68 | @end 69 | 70 | @implementation UBEExperimentExamples 71 | 72 | - (void)featureFlag_treated { 73 | NSLog(@"1"); 74 | 75 | NSLog(@"2"); 76 | 77 | NSLog(@"3"); 78 | 79 | NSLog(@"4"); 80 | 81 | 82 | 83 | NSLog(@"7"); 84 | 85 | NSLog(@"8"); 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | int x; 96 | if (x) { 97 | NSLog(@"13"); 98 | } 99 | 100 | if (x) { 101 | NSLog(@"14"); 102 | } 103 | 104 | } 105 | 106 | - (void)featureFlag_control { 107 | NSLog(@"control"); 108 | } 109 | 110 | // Group names are TODO 111 | - (void)featureFlag_specificGroup { 112 | if ([self.experiments 113 | isInTreatmentGroup:@"groupname" 114 | forExperiment:UBExperimentNameSomething]) { 115 | NSLog(@"treated for groupname treatment group"); 116 | } 117 | } 118 | 119 | // Non boolean returns are TODO 120 | - (void)featureFlag_stringParameter { 121 | NSString *string = [self.experiments 122 | stringParameter:@"param_name" 123 | forExperiment:UBExperimentNameSomething 124 | withDefaultValue:@"default"]; 125 | NSLog(@"%@", string); 126 | } 127 | 128 | - (void)featureFlag_numberParameter { 129 | NSNumber *number = [self.experiments 130 | numberParameter:@"param_name" 131 | forExperiment:UBExperimentNameSomething 132 | withDefaultValue:@2]; 133 | NSLog(@"%@", number); 134 | } 135 | 136 | - (void)featureFlag_inclusions { 137 | 138 | 139 | } 140 | 141 | #pragma mark - Tests 142 | 143 | 144 | - (void)featureFlag_test { 145 | 146 | 147 | [self setNumberParameter:@"" value:@2 forExperimentNamed:@""]; 148 | [self enableExperimentNamed:@"experiment" 149 | forTreatmentGroup:@"treatmentGroup"]; 150 | } 151 | 152 | 153 | - (void)featureFlag_variants { 154 | [self testAllFeatureFlagVariants:^(NSArray *enabledFlags) { 155 | } 156 | featureFlagNames:@[ 157 | 158 | ]]; 159 | } 160 | 161 | - (void)featureFlag_snapshotVariants { 162 | [self snapshotTestAllFeatureFlagVariants:^(NSArray *enabledFlags, 163 | NSString *identifier) { 164 | // test code 165 | } 166 | featureFlagNames:@[ 167 | 168 | ]]; 169 | } 170 | 171 | #pragma mark - helpers 172 | 173 | // Enable a feature flag for all treatment groups. Cleared at the end of every 174 | // test 175 | - (void)enableFeatureFlagNamed:(NSString *)name { 176 | return; 177 | } 178 | 179 | // Enable an experiment for one treatment group. Cleared at the end of every 180 | // test 181 | - (void)enableExperimentNamed:(NSString *)name 182 | forTreatmentGroup:(NSString *)treatmentGroup { 183 | return; 184 | } 185 | 186 | // Enable an experiment for one treatment group. Cleared at the end of every 187 | // test 188 | - (void)setNumberParameter:(NSString *)parameterName 189 | value:(NSNumber *)number 190 | forExperimentNamed:(NSString *)name { 191 | return; 192 | } 193 | 194 | /** Puts experiment in control */ 195 | - (void)disableExperimentNamed:(NSString *)name { 196 | return; 197 | } 198 | 199 | typedef void (^FeatureFlagTestBlock)(NSArray *enabledFlags); 200 | typedef void (^SnapshotTestBlock)(NSArray *enabledFlags, 201 | NSString *identifier); 202 | 203 | // Runs the test block with all possible combinations of experiments enabled and 204 | // disabled. 205 | - (void)testAllFeatureFlagVariants:(FeatureFlagTestBlock)testBlock 206 | featureFlagNames:(NSArray *)featureFlagNames { 207 | return; 208 | } 209 | 210 | // Runs the test block with all possible combinations of experiments enabled and 211 | // disabled. Passes in an identifier for the snapshot 212 | - (void)snapshotTestAllFeatureFlagVariants:(SnapshotTestBlock)testBlock 213 | featureFlagNames: 214 | (NSArray *)featureFlagNames { 215 | return; 216 | } 217 | 218 | @end 219 | -------------------------------------------------------------------------------- /java/piranha/src/main/java/com/uber/piranha/UsageCounter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. You may obtain a copy of the License at 6 | * 7 | *

http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | *

Unless required by applicable law or agreed to in writing, software distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.uber.piranha; 15 | 16 | import com.google.common.base.Preconditions; 17 | import com.google.common.collect.ImmutableMap; 18 | import com.google.errorprone.VisitorState; 19 | import com.google.errorprone.util.ASTHelpers; 20 | import com.sun.source.tree.IdentifierTree; 21 | import com.sun.source.tree.MemberSelectTree; 22 | import com.sun.source.tree.MethodTree; 23 | import com.sun.source.tree.Tree; 24 | import com.sun.source.tree.VariableTree; 25 | import com.sun.source.util.TreePath; 26 | import com.sun.source.util.TreePathScanner; 27 | import com.sun.source.util.TreeScanner; 28 | import com.sun.tools.javac.code.Symbol; 29 | import java.util.HashMap; 30 | import java.util.LinkedHashMap; 31 | import java.util.LinkedHashSet; 32 | import java.util.Map; 33 | import java.util.Set; 34 | 35 | /** Originally from com.uber.errorprone.checker.dagger.UsageCounter */ 36 | public final class UsageCounter { 37 | 38 | private UsageCounter() { 39 | /* Helper class only, not instantiable */ 40 | } 41 | 42 | private static boolean symbolHasSuppressUnusedCheckerWarningsAnnotation( 43 | Symbol symbol, String checkerName) { 44 | SuppressWarnings annotation = symbol.getAnnotation(SuppressWarnings.class); 45 | if (annotation != null) { 46 | for (String s : annotation.value()) { 47 | if (s.equals(checkerName)) return true; 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | public static ImmutableMap getUsageCounts(VisitorState state) { 54 | return getUsageCounts(state, state.getPath()); 55 | } 56 | 57 | public static ImmutableMap getUsageCounts( 58 | VisitorState state, TreePath path) { 59 | UsageCounter.CallScanner callScanner = new UsageCounter.CallScanner(state); 60 | callScanner.scan(path, null); 61 | ImmutableMap.Builder builder = ImmutableMap.builder(); 62 | for (VariableTree decl : callScanner.declaredInjectVars) { 63 | Symbol s = ASTHelpers.getSymbol(decl); 64 | CounterData counterData = 65 | new CounterData( 66 | DeclType.FIELD, 67 | decl, 68 | (callScanner.usedVars.containsKey(s) ? callScanner.usedVars.get(s) : 0)); 69 | builder.put(s, counterData); 70 | } 71 | 72 | for (VariableTree decl : callScanner.declaredParamVars.keySet()) { 73 | Symbol s = ASTHelpers.getSymbol(decl); 74 | Symbol.MethodSymbol mSym = callScanner.declaredParamVars.get(decl); 75 | CounterData counterData = 76 | new CounterData( 77 | DeclType.PARAM, 78 | decl, 79 | (callScanner.usedVars.containsKey(s) ? callScanner.usedVars.get(s) : 0)); 80 | builder.put(s, counterData); 81 | } 82 | return builder.build(); 83 | } 84 | 85 | public static ImmutableMap getRawUsageCounts(Tree tree) { 86 | RawUsageCountsScanner scanner = new RawUsageCountsScanner(); 87 | scanner.scan(tree, null); 88 | return ImmutableMap.copyOf(scanner.usedVars); 89 | } 90 | 91 | private static void addUse(Map usedVars, Symbol symbol) { 92 | if (usedVars.containsKey(symbol)) { 93 | usedVars.put(symbol, usedVars.get(symbol) + 1); 94 | } else { 95 | usedVars.put(symbol, 1); 96 | } 97 | } 98 | 99 | static class CallScanner extends TreePathScanner { 100 | final Set declaredInjectVars = new LinkedHashSet<>(); 101 | final HashMap declaredParamVars = 102 | new HashMap(); 103 | final Map usedVars = new LinkedHashMap<>(); 104 | final VisitorState state; 105 | 106 | CallScanner(VisitorState state) { 107 | this.state = state; 108 | } 109 | 110 | @Override 111 | public Void visitMemberSelect(MemberSelectTree tree, Void unused) { 112 | addUse(usedVars, ASTHelpers.getSymbol(tree)); 113 | return super.visitMemberSelect(tree, null); 114 | } 115 | 116 | @Override 117 | public Void visitIdentifier(IdentifierTree tree, Void unused) { 118 | addUse(usedVars, ASTHelpers.getSymbol(tree)); 119 | return super.visitIdentifier(tree, null); 120 | } 121 | 122 | @Override 123 | public Void visitMethod(MethodTree tree, Void unused) { 124 | Symbol.MethodSymbol mSym = ASTHelpers.getSymbol(tree); 125 | if (ASTHelpers.hasAnnotation(mSym, "dagger.Provides", state)) { 126 | for (VariableTree vt : tree.getParameters()) { 127 | declaredParamVars.put(vt, mSym); 128 | } 129 | } 130 | return super.visitMethod(tree, null); 131 | } 132 | 133 | @Override 134 | public Void visitVariable(VariableTree tree, Void unused) { 135 | Symbol.VarSymbol vSym = ASTHelpers.getSymbol(tree); 136 | if (ASTHelpers.hasAnnotation(vSym, "javax.inject.Inject", state)) { 137 | declaredInjectVars.add(tree); 138 | } 139 | return super.visitVariable(tree, null); 140 | } 141 | } 142 | 143 | static class RawUsageCountsScanner extends TreeScanner { 144 | final Map usedVars = new LinkedHashMap<>(); 145 | 146 | @Override 147 | public Void visitMemberSelect(MemberSelectTree tree, Void unused) { 148 | addUse(usedVars, ASTHelpers.getSymbol(tree)); 149 | return super.visitMemberSelect(tree, null); 150 | } 151 | 152 | @Override 153 | public Void visitIdentifier(IdentifierTree tree, Void unused) { 154 | addUse(usedVars, ASTHelpers.getSymbol(tree)); 155 | return super.visitIdentifier(tree, null); 156 | } 157 | } 158 | 159 | public enum DeclType { 160 | FIELD, 161 | PARAM, 162 | } 163 | 164 | public static class CounterData { 165 | public final DeclType declType; 166 | public final VariableTree declaration; 167 | public final int count; 168 | 169 | public CounterData(DeclType declType, VariableTree declaration, int count) { 170 | Preconditions.checkArgument(count >= 0, "Count must be positive."); 171 | this.declType = declType; 172 | this.declaration = declaration; 173 | this.count = count; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /java/gradle/gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Chris Banes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | version = VERSION_NAME 21 | group = GROUP 22 | 23 | def isReleaseBuild() { 24 | return VERSION_NAME.contains("SNAPSHOT") == false 25 | } 26 | 27 | def getReleaseRepositoryUrl() { 28 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 29 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 30 | } 31 | 32 | def getSnapshotRepositoryUrl() { 33 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 34 | : "https://oss.sonatype.org/content/repositories/snapshots/" 35 | } 36 | 37 | def getRepositoryUsername() { 38 | return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : "" 39 | } 40 | 41 | def getRepositoryPassword() { 42 | return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : "" 43 | } 44 | 45 | afterEvaluate { project -> 46 | uploadArchives { 47 | repositories { 48 | mavenDeployer { 49 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 50 | 51 | pom.groupId = GROUP 52 | pom.artifactId = POM_ARTIFACT_ID 53 | pom.version = VERSION_NAME 54 | 55 | repository(url: getReleaseRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 59 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 60 | } 61 | 62 | pom.project { 63 | name POM_NAME 64 | packaging POM_PACKAGING 65 | description POM_DESCRIPTION 66 | url POM_URL 67 | 68 | scm { 69 | url POM_SCM_URL 70 | connection POM_SCM_CONNECTION 71 | developerConnection POM_SCM_DEV_CONNECTION 72 | } 73 | 74 | licenses { 75 | license { 76 | name POM_LICENCE_NAME 77 | url POM_LICENCE_URL 78 | distribution POM_LICENCE_DIST 79 | } 80 | } 81 | 82 | developers { 83 | developer { 84 | id POM_DEVELOPER_ID 85 | name POM_DEVELOPER_NAME 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | signing { 94 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 95 | sign configurations.archives 96 | } 97 | 98 | if (project.getPlugins().hasPlugin('com.android.application') || 99 | project.getPlugins().hasPlugin('com.android.library')) { 100 | task install(type: Upload, dependsOn: assemble) { 101 | repositories.mavenInstaller { 102 | configuration = configurations.archives 103 | 104 | pom.groupId = GROUP 105 | pom.artifactId = POM_ARTIFACT_ID 106 | pom.version = VERSION_NAME 107 | 108 | pom.project { 109 | name POM_NAME 110 | packaging POM_PACKAGING 111 | description POM_DESCRIPTION 112 | url POM_URL 113 | 114 | scm { 115 | url POM_SCM_URL 116 | connection POM_SCM_CONNECTION 117 | developerConnection POM_SCM_DEV_CONNECTION 118 | } 119 | 120 | licenses { 121 | license { 122 | name POM_LICENCE_NAME 123 | url POM_LICENCE_URL 124 | distribution POM_LICENCE_DIST 125 | } 126 | } 127 | 128 | developers { 129 | developer { 130 | id POM_DEVELOPER_ID 131 | name POM_DEVELOPER_NAME 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | task androidJavadocs(type: Javadoc) { 139 | if (!project.plugins.hasPlugin('kotlin-android')) { 140 | source = android.sourceSets.main.java.srcDirs 141 | } 142 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 143 | exclude '**/internal/*' 144 | 145 | if (JavaVersion.current().isJava8Compatible()) { 146 | options.addStringOption('Xdoclint:none', '-quiet') 147 | } 148 | } 149 | 150 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 151 | classifier = 'javadoc' 152 | from androidJavadocs.destinationDir 153 | } 154 | 155 | task androidSourcesJar(type: Jar) { 156 | classifier = 'sources' 157 | from android.sourceSets.main.java.sourceFiles 158 | } 159 | } else { 160 | install { 161 | repositories.mavenInstaller { 162 | pom.groupId = GROUP 163 | pom.artifactId = POM_ARTIFACT_ID 164 | pom.version = VERSION_NAME 165 | 166 | pom.project { 167 | name POM_NAME 168 | packaging POM_PACKAGING 169 | description POM_DESCRIPTION 170 | url POM_URL 171 | 172 | scm { 173 | url POM_SCM_URL 174 | connection POM_SCM_CONNECTION 175 | developerConnection POM_SCM_DEV_CONNECTION 176 | } 177 | 178 | licenses { 179 | license { 180 | name POM_LICENCE_NAME 181 | url POM_LICENCE_URL 182 | distribution POM_LICENCE_DIST 183 | } 184 | } 185 | 186 | developers { 187 | developer { 188 | id POM_DEVELOPER_ID 189 | name POM_DEVELOPER_NAME 190 | } 191 | } 192 | } 193 | } 194 | } 195 | 196 | task sourcesJar(type: Jar, dependsOn: classes) { 197 | classifier = 'sources' 198 | from sourceSets.main.allSource 199 | } 200 | 201 | task javadocJar(type: Jar, dependsOn: javadoc) { 202 | classifier = 'javadoc' 203 | from javadoc.destinationDir 204 | } 205 | } 206 | 207 | if (JavaVersion.current().isJava8Compatible()) { 208 | allprojects { 209 | tasks.withType(Javadoc) { 210 | options.addStringOption('Xdoclint:none', '-quiet') 211 | } 212 | } 213 | } 214 | 215 | artifacts { 216 | if (project.getPlugins().hasPlugin('com.android.application') || 217 | project.getPlugins().hasPlugin('com.android.library')) { 218 | archives androidSourcesJar 219 | archives androidJavadocsJar 220 | } else { 221 | archives sourcesJar 222 | archives javadocJar 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /java/README.md: -------------------------------------------------------------------------------- 1 | # PiranhaJava 2 | 3 | ## Installation 4 | 5 | ### Overview 6 | 7 | Piranha requires that you build your code with [Error Prone](http://errorprone.info), version 2.3.2 or higher. See the [Error Prone documentation](http://errorprone.info/docs/installation) for instructions on getting started with Error Prone and integration with your build system. 8 | 9 | ### Gradle 10 | 11 | To integrate Piranha into your Java project you'll need a version of the following additions to your `build.gradle` file: 12 | 13 | ``` 14 | plugins { 15 | id "com.github.sherter.google-java-format" version "0.7.1" 16 | id "net.ltgt.errorprone" version "0.6" apply false 17 | id "java" 18 | } 19 | 20 | sourceCompatibility = "1.8" 21 | targetCompatibility = "1.8" 22 | 23 | dependencies { 24 | annotationProcessor "com.uber.piranha:piranha:0.0.3" 25 | errorprone "com.google.errorprone:error_prone_core:2.3.2" 26 | errorproneJavac "com.google.errorprone:javac:9+181-r4173-1" 27 | } 28 | 29 | import net.ltgt.gradle.errorprone.CheckSeverity 30 | 31 | tasks.withType(JavaCompile) { 32 | options.errorprone { 33 | check("Piranha", CheckSeverity.WARN) 34 | } 35 | options.errorprone.errorproneArgs << "-XepPatchChecks:Piranha" 36 | options.errorprone.errorproneArgs << "-XepPatchLocation:IN_PLACE" 37 | // The lines below should be replaced by code that loads the specific flag to patch 38 | // and final treatment condition. 39 | options.errorprone.errorproneArgs << "-XepOpt:Piranha:FlagName=SAMPLE_STALE_FLAG" 40 | options.errorprone.errorproneArgs << "-XepOpt:Piranha:IsTreated=true" 41 | options.errorprone.errorproneArgs << "-XepOpt:Piranha:Config=config/piranha.properties" 42 | } 43 | ``` 44 | 45 | The `plugins` section pulls in the [Gradle Error Prone plugin](https://github.com/tbroyer/gradle-errorprone-plugin) for Error Prone integration. In `dependencies`, the `annotationProcessor` line loads Piranha, the `errorprone` line ensures that a compatible version of Error Prone is used, and the `errorproneJavac` line is needed for JDK 8 compatibility. 46 | 47 | In the `tasks.withType(JavaCompile)` section, we pass some configuration options to Piranha. First `check("Piranha", CheckSeverity.WARN)` sets Piranha issues to the warning level. Then, `option.errorprone.errorproneArgs` is used to add a set of arguments to Piranha. `XepPatchChecks:Piranha` and `-XepPatchLocation:IN_PLACE` arguments are used together to enable in-place refactoring of the code. `-XepOpt:Piranha:FlagName` is used to specify a stale flag name that is used in the code, `-XepOpt:Piranha:IsTreated` is used to specify whether the treatment (`true`) branch or the control (`false`) branch needs to be taken during refactoring. Then `-XepOpt:Piranha:Config` is used to provide the properties file which specifies the APIs and annotations that are considered for refactoring. 48 | 49 | The properties file has the following template: 50 | 51 | ``` 52 | treatedMethods=treated,flagEnabled 53 | controlMethods=flagDisabled 54 | emptyMethods=enableFlag,disableFlag 55 | treatmentGroupMethods=isToggleInGroup 56 | annotations=FlagTesting 57 | linkURL= 58 | ``` 59 | 60 | The `treatedMethods` are the APIs which correspond to the treatment behavior of the flag, `controlMethods` correspond to the control behavior of the flag. In the above example, the API `flagEnabled` corresponds to treatment behavior. Hence, when `IsTreated` flag is set to `true`, `flagEnabled(SAMPLE_STALE_FLAG)` will be evaluated to `true`. Similarly, `flagDisabled(SAMPLE_STALE_FLAG)` which corresponds to the control behavior will evaluate to `false`. 61 | 62 | The `emptyMethods` specify the APIs which need to be discarded from the code. For example, a statement `enableFlag(SAMPLE_STALE_FLAG);` will be deleted from the code. 63 | 64 | The `annotations` specify the annotations used (e.g., in unit testing) to determine treatment or control behavior. For example: 65 | 66 | ``` 67 | @FlagTesting(treated = TestExperimentName.SAMPLE_STALE_FLAG) 68 | public void some_unit_test() { ... } 69 | ``` 70 | 71 | will be refactored to 72 | 73 | ``` 74 | public void some_unit_test() { ... } 75 | ``` 76 | 77 | when `IsTreated` is `true`, and will be deleted completely when `IsTreated` is `false`. 78 | 79 | Finally, the setting `linkURL` in the properties file is to provide a URL describing the Piranha tooling and any custom configurations associated with the codebase. 80 | 81 | 82 | ## Example refactoring 83 | 84 | Consider a simple example 85 | 86 | ``` 87 | public class MyClass { 88 | private XPTest expt; 89 | ... 90 | public void foo() { 91 | if(expt.flagEnabled(TestExperimentName.SAMPLE_STALE_FLAG)) { 92 | System.out.println("Hello World"); 93 | } 94 | } 95 | 96 | public void bar() { 97 | if(expt.flagDisabled(TestExperimentName.SAMPLE_STALE_FLAG)) { 98 | System.out.println("Hi World"); 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | and the following arguments to Piranha 105 | 106 | ``` 107 | options.errorprone.errorproneArgs << "-XepOpt:Piranha:FlagName=SAMPLE_STALE_FLAG" 108 | options.errorprone.errorproneArgs << "-XepOpt:Piranha:IsTreated=true" 109 | options.errorprone.errorproneArgs << "-XepOpt:Piranha:Config=config/piranha.properties 110 | ``` 111 | where `piranha.properties` contains the following, 112 | 113 | ``` 114 | treatedMethods=treated,flagEnabled 115 | controlMethods=flagDisabled 116 | emptyMethods=enableFlag,disableFlag 117 | annotations=FlagTesting 118 | linkURL= 119 | ``` 120 | 121 | the refactored output will be 122 | 123 | ``` 124 | public class MyClass { 125 | private XPTest expt; 126 | ... 127 | public void foo() { 128 | System.out.println("Hello World"); 129 | } 130 | 131 | public void bar() { 132 | } 133 | } 134 | ``` 135 | 136 | When `IsTreated` is `false`, then the refactored output will be 137 | 138 | ``` 139 | public class MyClass { 140 | private XPTest expt; 141 | ... 142 | public void foo() { 143 | } 144 | 145 | public void bar() { 146 | System.out.println("Hi World"); 147 | } 148 | } 149 | ``` 150 | 151 | This example is present in the [sample](https://github.com/uber/piranha/tree/master/java/sample/) directory. 152 | 153 | ### Maven Instructions 154 | 155 | * For the example Piranha configuration discussed above, follow steps given for the ErrorProne [example](https://github.com/google/error-prone/blob/master/examples/maven/pom.xml) to setup the `pom.xml` to run with ErrorProne. 156 | 157 | * Update the `pom.xml` with Piranha related configuration. It will be as follows: 158 | ``` 159 | 160 | 161 | 162 | org.apache.maven.plugins 163 | maven-compiler-plugin 164 | 3.8.0 165 | 166 | 8 167 | 8 168 | true 169 | 170 | -XDcompilePolicy=simple 171 | -Xplugin:ErrorProne -Xep:Piranha:WARN -XepPatchChecks:Piranha -XepPatchLocation:IN_PLACE -XepOpt:Piranha:FlagName=SAMPLE_STALE_FLAG -XepOpt:Piranha:IsTreated=true -XepOpt:Piranha:Config=config/piranha.properties 172 | 173 | 174 | 175 | com.uber.piranha 176 | piranha 177 | 0.0.3 178 | 179 | 180 | com.google.errorprone 181 | error_prone_core 182 | 2.3.2 183 | 184 | 185 | 186 | 187 | 188 | 189 | ``` 190 | -------------------------------------------------------------------------------- /objc/tests/Control.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | #define UBEEXPERIMENT_NAMES_APP \ 21 | X(UBExperimentNameSomething, @"some_random_experiment") 22 | 23 | #define X(name, value) extern NSString *const name; 24 | UBEEXPERIMENT_NAMES_APP 25 | #undef X 26 | 27 | @interface Test : NSObject 28 | 29 | - (id)andReturn:(id)anObject; 30 | - (id)andReturnValue:(NSValue *)aValue; 31 | 32 | @property (nonatomic, readonly) Test *(^ _andReturn)(id); 33 | 34 | @end 35 | 36 | @protocol UBCachedExperimenting 37 | 38 | - (BOOL)isValidExperiment:(NSString *)experimentKey; 39 | 40 | - (BOOL)isInControlGroupForExperiment:(NSString *)experimentKey; 41 | 42 | - (BOOL)isTreatedForExperiment:(NSString *)experimentKey; 43 | 44 | - (BOOL)isInTreatmentGroup:(NSString *)treatmentGroupKey 45 | forExperiment:(NSString *)experimentKey; 46 | 47 | - (nullable NSString *)stringParameter:(NSString *)parameterName 48 | forExperiment:(NSString *)experimentKey 49 | withDefaultValue:(nullable NSString *)defaultValue; 50 | 51 | - (nullable NSNumber *)numberParameter:(NSString *)parameterName 52 | forExperiment:(NSString *)experimentKey 53 | withDefaultValue:(nullable NSNumber *)defaultValue; 54 | 55 | - (void)sendInclusionEventForExperiment:(NSString *)experimentKey; 56 | 57 | - (void)sendInclusionEventForExperiment:(NSString *)experimentKey 58 | forTreatmentGroup:(NSString *)treatmentGroupKey; 59 | 60 | @end 61 | 62 | 63 | @interface UBEExperimentExamples : NSObject 64 | 65 | @property(nonatomic, readonly) id experiments; 66 | @property(nonatomic, readonly) id experimentsMock; 67 | 68 | @end 69 | 70 | @implementation UBEExperimentExamples 71 | 72 | - (void)featureFlag_treated { 73 | if ([self.experiments 74 | isTreatedForExperiment:UBExperimentNameSomething]) { 75 | NSLog(@"1"); 76 | } 77 | 78 | if ([self.experiments 79 | isTreatedForExperiment:UBExperimentNameSomething] || true) { 80 | NSLog(@"2"); 81 | } 82 | 83 | if ([self.experiments 84 | isTreatedForExperiment:UBExperimentNameSomething] && true) { 85 | NSLog(@"3"); 86 | } 87 | 88 | if ([self.experiments 89 | isTreatedForExperiment:UBExperimentNameSomething] || false) { 90 | NSLog(@"4"); 91 | } 92 | 93 | if ([self.experiments 94 | isTreatedForExperiment:UBExperimentNameSomething] && false) { 95 | NSLog(@"5"); 96 | } 97 | 98 | if (![self.experiments 99 | isTreatedForExperiment:UBExperimentNameSomething]) { 100 | NSLog(@"6"); 101 | } else { 102 | NSLog(@"7"); 103 | } 104 | 105 | if (![self.experiments 106 | isTreatedForExperiment:UBExperimentNameSomething] || true) { 107 | NSLog(@"8"); 108 | } 109 | 110 | if (![self.experiments 111 | isTreatedForExperiment:UBExperimentNameSomething] && true) { 112 | NSLog(@"9"); 113 | } 114 | 115 | if (![self.experiments 116 | isTreatedForExperiment:UBExperimentNameSomething] || false) { 117 | NSLog(@"10"); 118 | } 119 | 120 | if (![self.experiments 121 | isTreatedForExperiment:UBExperimentNameSomething] && false) { 122 | NSLog(@"11"); 123 | } 124 | 125 | if (false || ![self.experiments 126 | isTreatedForExperiment:UBExperimentNameSomething]) { 127 | NSLog(@"12"); 128 | } 129 | 130 | int x; 131 | if (x || ![self.experiments 132 | isTreatedForExperiment:UBExperimentNameSomething]) { 133 | NSLog(@"13"); 134 | } 135 | 136 | if (x && [self.experiments 137 | isTreatedForExperiment:UBExperimentNameSomething]) { 138 | NSLog(@"14"); 139 | } 140 | 141 | } 142 | 143 | - (void)featureFlag_control { 144 | if (![self.experiments isInControlGroupForExperiment: 145 | UBExperimentNameSomething]) { 146 | NSLog(@"control"); 147 | } 148 | } 149 | 150 | // Group names are TODO 151 | - (void)featureFlag_specificGroup { 152 | if ([self.experiments 153 | isInTreatmentGroup:@"groupname" 154 | forExperiment:UBExperimentNameSomething]) { 155 | NSLog(@"treated for groupname treatment group"); 156 | } 157 | } 158 | 159 | // Non boolean returns are TODO 160 | - (void)featureFlag_stringParameter { 161 | NSString *string = [self.experiments 162 | stringParameter:@"param_name" 163 | forExperiment:UBExperimentNameSomething 164 | withDefaultValue:@"default"]; 165 | NSLog(@"%@", string); 166 | } 167 | 168 | - (void)featureFlag_numberParameter { 169 | NSNumber *number = [self.experiments 170 | numberParameter:@"param_name" 171 | forExperiment:UBExperimentNameSomething 172 | withDefaultValue:@2]; 173 | NSLog(@"%@", number); 174 | } 175 | 176 | - (void)featureFlag_inclusions { 177 | [self.experiments sendInclusionEventForExperiment: 178 | UBExperimentNameSomething]; 179 | [self.experiments 180 | sendInclusionEventForExperiment:UBExperimentNameSomething 181 | forTreatmentGroup:@"groupname"]; 182 | } 183 | 184 | #pragma mark - Tests 185 | 186 | 187 | - (void)featureFlag_test { 188 | [self enableFeatureFlagNamed:UBExperimentNameSomething]; 189 | [self disableExperimentNamed:UBExperimentNameSomething]; 190 | [self setNumberParameter:@"" value:@2 forExperimentNamed:@""]; 191 | [self enableExperimentNamed:@"experiment" 192 | forTreatmentGroup:@"treatmentGroup"]; 193 | } 194 | 195 | 196 | - (void)featureFlag_variants { 197 | [self testAllFeatureFlagVariants:^(NSArray *enabledFlags) { 198 | } 199 | featureFlagNames:@[ 200 | UBExperimentNameSomething 201 | ]]; 202 | } 203 | 204 | - (void)featureFlag_snapshotVariants { 205 | [self snapshotTestAllFeatureFlagVariants:^(NSArray *enabledFlags, 206 | NSString *identifier) { 207 | // test code 208 | } 209 | featureFlagNames:@[ 210 | UBExperimentNameSomething 211 | ]]; 212 | } 213 | 214 | #pragma mark - helpers 215 | 216 | // Enable a feature flag for all treatment groups. Cleared at the end of every 217 | // test 218 | - (void)enableFeatureFlagNamed:(NSString *)name { 219 | return; 220 | } 221 | 222 | // Enable an experiment for one treatment group. Cleared at the end of every 223 | // test 224 | - (void)enableExperimentNamed:(NSString *)name 225 | forTreatmentGroup:(NSString *)treatmentGroup { 226 | return; 227 | } 228 | 229 | // Enable an experiment for one treatment group. Cleared at the end of every 230 | // test 231 | - (void)setNumberParameter:(NSString *)parameterName 232 | value:(NSNumber *)number 233 | forExperimentNamed:(NSString *)name { 234 | return; 235 | } 236 | 237 | /** Puts experiment in control */ 238 | - (void)disableExperimentNamed:(NSString *)name { 239 | return; 240 | } 241 | 242 | typedef void (^FeatureFlagTestBlock)(NSArray *enabledFlags); 243 | typedef void (^SnapshotTestBlock)(NSArray *enabledFlags, 244 | NSString *identifier); 245 | 246 | // Runs the test block with all possible combinations of experiments enabled and 247 | // disabled. 248 | - (void)testAllFeatureFlagVariants:(FeatureFlagTestBlock)testBlock 249 | featureFlagNames:(NSArray *)featureFlagNames { 250 | return; 251 | } 252 | 253 | // Runs the test block with all possible combinations of experiments enabled and 254 | // disabled. Passes in an identifier for the snapshot 255 | - (void)snapshotTestAllFeatureFlagVariants:(SnapshotTestBlock)testBlock 256 | featureFlagNames: 257 | (NSArray *)featureFlagNames { 258 | return; 259 | } 260 | 261 | @end 262 | -------------------------------------------------------------------------------- /objc/tests/Treated.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | #define UBEEXPERIMENT_NAMES_APP \ 21 | X(UBExperimentNameSomething, @"some_random_experiment") 22 | 23 | #define X(name, value) extern NSString *const name; 24 | UBEEXPERIMENT_NAMES_APP 25 | #undef X 26 | 27 | @interface Test : NSObject 28 | 29 | - (id)andReturn:(id)anObject; 30 | - (id)andReturnValue:(NSValue *)aValue; 31 | 32 | @property (nonatomic, readonly) Test *(^ _andReturn)(id); 33 | 34 | @end 35 | 36 | @protocol UBCachedExperimenting 37 | 38 | - (BOOL)isValidExperiment:(NSString *)experimentKey; 39 | 40 | - (BOOL)isInControlGroupForExperiment:(NSString *)experimentKey; 41 | 42 | - (BOOL)isTreatedForExperiment:(NSString *)experimentKey; 43 | 44 | - (BOOL)isInTreatmentGroup:(NSString *)treatmentGroupKey 45 | forExperiment:(NSString *)experimentKey; 46 | 47 | - (nullable NSString *)stringParameter:(NSString *)parameterName 48 | forExperiment:(NSString *)experimentKey 49 | withDefaultValue:(nullable NSString *)defaultValue; 50 | 51 | - (nullable NSNumber *)numberParameter:(NSString *)parameterName 52 | forExperiment:(NSString *)experimentKey 53 | withDefaultValue:(nullable NSNumber *)defaultValue; 54 | 55 | - (void)sendInclusionEventForExperiment:(NSString *)experimentKey; 56 | 57 | - (void)sendInclusionEventForExperiment:(NSString *)experimentKey 58 | forTreatmentGroup:(NSString *)treatmentGroupKey; 59 | 60 | @end 61 | 62 | 63 | @interface UBEExperimentExamples : NSObject 64 | 65 | @property(nonatomic, readonly) id experiments; 66 | @property(nonatomic, readonly) id experimentsMock; 67 | 68 | @end 69 | 70 | @implementation UBEExperimentExamples 71 | 72 | - (void)featureFlag_treated { 73 | if ([self.experiments 74 | isTreatedForExperiment:UBExperimentNameSomething]) { 75 | NSLog(@"1"); 76 | } 77 | 78 | if ([self.experiments 79 | isTreatedForExperiment:UBExperimentNameSomething] || true) { 80 | NSLog(@"2"); 81 | } 82 | 83 | if ([self.experiments 84 | isTreatedForExperiment:UBExperimentNameSomething] && true) { 85 | NSLog(@"3"); 86 | } 87 | 88 | if ([self.experiments 89 | isTreatedForExperiment:UBExperimentNameSomething] || false) { 90 | NSLog(@"4"); 91 | } 92 | 93 | if ([self.experiments 94 | isTreatedForExperiment:UBExperimentNameSomething] && false) { 95 | NSLog(@"5"); 96 | } 97 | 98 | if (![self.experiments 99 | isTreatedForExperiment:UBExperimentNameSomething]) { 100 | NSLog(@"6"); 101 | } else { 102 | NSLog(@"7"); 103 | } 104 | 105 | if (![self.experiments 106 | isTreatedForExperiment:UBExperimentNameSomething] || true) { 107 | NSLog(@"8"); 108 | } 109 | 110 | if (![self.experiments 111 | isTreatedForExperiment:UBExperimentNameSomething] && true) { 112 | NSLog(@"9"); 113 | } 114 | 115 | if (![self.experiments 116 | isTreatedForExperiment:UBExperimentNameSomething] || false) { 117 | NSLog(@"10"); 118 | } 119 | 120 | if (![self.experiments 121 | isTreatedForExperiment:UBExperimentNameSomething] && false) { 122 | NSLog(@"11"); 123 | } 124 | 125 | if (false || ![self.experiments 126 | isTreatedForExperiment:UBExperimentNameSomething]) { 127 | NSLog(@"12"); 128 | } 129 | 130 | int x; 131 | if (x || ![self.experiments 132 | isTreatedForExperiment:UBExperimentNameSomething]) { 133 | NSLog(@"13"); 134 | } 135 | 136 | if (x && [self.experiments 137 | isTreatedForExperiment:UBExperimentNameSomething]) { 138 | NSLog(@"14"); 139 | } 140 | 141 | } 142 | 143 | - (void)featureFlag_control { 144 | if (![self.experiments isInControlGroupForExperiment: 145 | UBExperimentNameSomething]) { 146 | NSLog(@"control"); 147 | } 148 | } 149 | 150 | // Group names are TODO 151 | - (void)featureFlag_specificGroup { 152 | if ([self.experiments 153 | isInTreatmentGroup:@"groupname" 154 | forExperiment:UBExperimentNameSomething]) { 155 | NSLog(@"treated for groupname treatment group"); 156 | } 157 | } 158 | 159 | // Non boolean returns are TODO 160 | - (void)featureFlag_stringParameter { 161 | NSString *string = [self.experiments 162 | stringParameter:@"param_name" 163 | forExperiment:UBExperimentNameSomething 164 | withDefaultValue:@"default"]; 165 | NSLog(@"%@", string); 166 | } 167 | 168 | - (void)featureFlag_numberParameter { 169 | NSNumber *number = [self.experiments 170 | numberParameter:@"param_name" 171 | forExperiment:UBExperimentNameSomething 172 | withDefaultValue:@2]; 173 | NSLog(@"%@", number); 174 | } 175 | 176 | - (void)featureFlag_inclusions { 177 | [self.experiments sendInclusionEventForExperiment: 178 | UBExperimentNameSomething]; 179 | [self.experiments 180 | sendInclusionEventForExperiment:UBExperimentNameSomething 181 | forTreatmentGroup:@"groupname"]; 182 | } 183 | 184 | #pragma mark - Tests 185 | 186 | 187 | - (void)featureFlag_test { 188 | [self enableFeatureFlagNamed:UBExperimentNameSomething]; 189 | [self disableExperimentNamed:UBExperimentNameSomething]; 190 | [self setNumberParameter:@"" value:@2 forExperimentNamed:@""]; 191 | [self enableExperimentNamed:@"experiment" 192 | forTreatmentGroup:@"treatmentGroup"]; 193 | } 194 | 195 | 196 | - (void)featureFlag_variants { 197 | [self testAllFeatureFlagVariants:^(NSArray *enabledFlags) { 198 | } 199 | featureFlagNames:@[ 200 | UBExperimentNameSomething 201 | ]]; 202 | } 203 | 204 | - (void)featureFlag_snapshotVariants { 205 | [self snapshotTestAllFeatureFlagVariants:^(NSArray *enabledFlags, 206 | NSString *identifier) { 207 | // test code 208 | } 209 | featureFlagNames:@[ 210 | UBExperimentNameSomething 211 | ]]; 212 | } 213 | 214 | #pragma mark - helpers 215 | 216 | // Enable a feature flag for all treatment groups. Cleared at the end of every 217 | // test 218 | - (void)enableFeatureFlagNamed:(NSString *)name { 219 | return; 220 | } 221 | 222 | // Enable an experiment for one treatment group. Cleared at the end of every 223 | // test 224 | - (void)enableExperimentNamed:(NSString *)name 225 | forTreatmentGroup:(NSString *)treatmentGroup { 226 | return; 227 | } 228 | 229 | // Enable an experiment for one treatment group. Cleared at the end of every 230 | // test 231 | - (void)setNumberParameter:(NSString *)parameterName 232 | value:(NSNumber *)number 233 | forExperimentNamed:(NSString *)name { 234 | return; 235 | } 236 | 237 | /** Puts experiment in control */ 238 | - (void)disableExperimentNamed:(NSString *)name { 239 | return; 240 | } 241 | 242 | typedef void (^FeatureFlagTestBlock)(NSArray *enabledFlags); 243 | typedef void (^SnapshotTestBlock)(NSArray *enabledFlags, 244 | NSString *identifier); 245 | 246 | // Runs the test block with all possible combinations of experiments enabled and 247 | // disabled. 248 | - (void)testAllFeatureFlagVariants:(FeatureFlagTestBlock)testBlock 249 | featureFlagNames:(NSArray *)featureFlagNames { 250 | return; 251 | } 252 | 253 | // Runs the test block with all possible combinations of experiments enabled and 254 | // disabled. Passes in an identifier for the snapshot 255 | - (void)snapshotTestAllFeatureFlagVariants:(SnapshotTestBlock)testBlock 256 | featureFlagNames: 257 | (NSArray *)featureFlagNames { 258 | return; 259 | } 260 | 261 | @end 262 | -------------------------------------------------------------------------------- /swift/tests/control.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // swiftlint:disable custom_rules 18 | 19 | import Foundation 20 | 21 | protocol ExperimentKeying { 22 | var asString: String { get } 23 | } 24 | 25 | enum ExperimentNamesSwift: String, ExperimentKeying { 26 | //comment0 27 | case random_flag //comment1 28 | 29 | //comment4 30 | case test_experiment_suffix //comment5 31 | case test_second_experiment 32 | 33 | var asString: String { 34 | return String(describing: self) 35 | } 36 | } 37 | 38 | protocol TreatmentGroupKeying { 39 | var asString: String { get } 40 | } 41 | 42 | protocol ParamManager { 43 | func getParam(String param: String, defaultValue: Int) -> Bool 44 | } 45 | 46 | extension ParamManager { 47 | func getParam(String param: paramString, defaultValue: Int) -> Bool { 48 | return true 49 | } 50 | } 51 | 52 | protocol CachedExperimenting { 53 | func isInControlGroup(forExperiment experimentKey: ExperimentKeying) -> Bool 54 | func isTreated(forExperiment experimentKey: ExperimentKeying) -> Bool 55 | func addTreatedExperiment(forExperiment experimentKey: ExperimentKeying) -> Bool 56 | func removeTreatedExperiment(forExperiment experimentKey: ExperimentKeying) -> Bool 57 | func isInTreatmentGroup(treatmentGroup treatmentGroupKey: TreatmentGroupKeying, forExperiment experimentKey: ExperimentKeying) -> Bool 58 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: String) -> String 59 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Int) -> Int 60 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Float) -> Float 61 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Double) -> Double 62 | func sendInclusionEvent(forExperiment experimentKey: ExperimentKeying, treatmentGroup treatmentGroupKey: TreatmentGroupKeying) 63 | } 64 | 65 | extension CachedExperimenting { 66 | 67 | func isInControlGroup(forExperiment experimentKey: ExperimentKeying) -> Bool { 68 | return true 69 | } 70 | 71 | func isTreated(forExperiment experimentKey: ExperimentKeying) -> Bool { 72 | return true 73 | } 74 | 75 | func isInTreatmentGroup(treatmentGroup treatmentGroupKey: TreatmentGroupKeying, forExperiment experimentKey: ExperimentKeying) -> Bool { 76 | return true 77 | } 78 | 79 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: String) -> String { 80 | return "foobar" 81 | } 82 | 83 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Int) -> Int { 84 | return 42 85 | } 86 | 87 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Float) -> Float { 88 | return 42.0 89 | } 90 | 91 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Double) -> Double { 92 | return 42.0 93 | } 94 | 95 | func sendInclusionEvent(forExperiment experimentKey: ExperimentKeying, treatmentGroup treatmentGroupKey: TreatmentGroupKeying) { 96 | } 97 | } 98 | 99 | class CachedExperiments: CachedExperimenting { 100 | 101 | init() { 102 | } 103 | 104 | } 105 | 106 | class SwiftExamples { 107 | 108 | private let impressionStr: String 109 | 110 | let cachedExperiments = CachedExperiments() 111 | 112 | public enum test_12experiment: String { 113 | case delay 114 | } 115 | 116 | let p1 = "p1" 117 | static var p2 = "p2" 118 | 119 | private let fieldZ: Bool 120 | 121 | func test_expressions() { 122 | 123 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment_suffix) { 124 | print("treated") 125 | } 126 | print("not treated / control") 127 | 128 | let x = false 129 | var y = false 130 | 131 | if x { 132 | print("test 1") 133 | } 134 | 135 | if x || y { 136 | print("test 11") 137 | } 138 | 139 | y = false 140 | y = false 141 | y = x 142 | print("not treated / control") 143 | return 144 | } 145 | 146 | func addTreatedExperiment(forExperiment experimentKey: ExperimentKeying) -> Bool { 147 | return true 148 | 149 | } 150 | 151 | func test_additional() -> Bool { 152 | 153 | var platformUIChange = true 154 | var recordMode = true 155 | recordMode = false || platformUIChange 156 | 157 | recordMode = platformUIChange 158 | recordMode = recordMode 159 | 160 | recordMode = platformUIChange == recordMode ? platformUIChange : recordMode 161 | print("not treated / control") 162 | 163 | if recordMode { 164 | } else { 165 | 166 | } 167 | 168 | print("not treated / control") 169 | 170 | return false 171 | } 172 | 173 | func test_closure() -> Bool { 174 | 175 | cachedExperimentingMock.isTreatedHandler = { (key: ExperimentKeying) in 176 | return key.asString == ExperimentNamesSwift.test_second_experiment.asString 177 | } 178 | return true 179 | 180 | } 181 | 182 | // test for T2205641 183 | func test_experimentParameter() -> Bool { 184 | return true 185 | } 186 | 187 | func test_T2206585() -> Bool { 188 | print("br2") 189 | print("br4") 190 | print("br6") 191 | print("br8") 192 | 193 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) { 194 | print("br9") 195 | } else { 196 | print("br10") 197 | } 198 | 199 | return true 200 | 201 | } 202 | 203 | // tests for task T2191251 204 | func storeuse_before() { 205 | print("somestring2") 206 | print("somestring4") 207 | 208 | if self.fieldZ { 209 | print("fieldXfieldYfieldZ") 210 | } 211 | 212 | if fieldZ { 213 | print("pqr") 214 | } 215 | } 216 | 217 | func storeuse_init() { 218 | fieldZ = cachedexperiments.isTreated(forexperiment: ExperimentNamesSwift.test_experiment2) 219 | } 220 | 221 | func storeuse_after() { 222 | print("somestring2") 223 | print("somestring4") 224 | 225 | if self.fieldZ { 226 | print("XYZ") 227 | } 228 | 229 | if fieldZ { 230 | print("pqr") 231 | } 232 | } 233 | 234 | func test_nilcoalescing() { 235 | print("control1") 236 | print("treated2") 237 | print("treated3") 238 | print("control4") 239 | print("control5") 240 | print("treated6") 241 | 242 | if cachedExperiments?.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) ?? false { 243 | print("treated1") 244 | } else { 245 | print("control1") 246 | } 247 | 248 | var v = false 249 | v = true 250 | 251 | v = cachedExperiments?.isInControlGroup(forExperiment: ExperimentNamesSwift.test_second_experiment) ?? false 252 | } 253 | 254 | private let conj1: Bool 255 | private let conj2: Bool 256 | private let conj3: Bool 257 | 258 | func test_T2282603() { 259 | self.conj1 = cachedexperiments.isInControlGroup(forexperiment: ExperimentNamesSwift.test_second_experiment) 260 | self.conj2 = false 261 | self.conj3 = cachedexperiments.isInControlGroup(forexperiment: ExperimentNamesSwift.test_experiment_suffix) 262 | 263 | } 264 | 265 | // Test for T2606011 266 | private var shouldDoSomething: Bool { 267 | return false 268 | } 269 | 270 | private let engineeringFlags: [ExperimentNamesLoyalty] = [ 271 | .loyalty_credits_purchase_selection_rib_refactor, 272 | .loyalty_card_banner_impression_fix, 273 | .loyalty_credits_purchase_selection_default_payment_profile_fix, 274 | .loyalty_credits_purchase_addon_explicit_layout, 275 | .loyalty_stack_view_migration 276 | ] 277 | } 278 | -------------------------------------------------------------------------------- /java/piranha/src/test/resources/com/uber/piranha/XPFlagCleanerPositiveCases.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. You may obtain a copy of the License at 6 | * 7 | *

http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | *

Unless required by applicable law or agreed to in writing, software distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.uber.piranha; 15 | 16 | import dagger.Provides; 17 | import java.lang.annotation.ElementType; 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | import java.lang.annotation.Target; 21 | import javax.inject.Inject; 22 | 23 | class XPFlagCleanerPositiveCases { 24 | 25 | enum TestExperimentName { 26 | // BUG: Diagnostic contains: Cleans stale XP flags 27 | STALE_FLAG 28 | } 29 | 30 | enum AnotherTestExperimentName { 31 | // BUG: Diagnostic contains: Cleans stale XP flags 32 | @Autorollout 33 | STALE_FLAG 34 | } 35 | 36 | @Retention(RetentionPolicy.RUNTIME) 37 | public @interface Autorollout { 38 | boolean staged() default false; 39 | } 40 | 41 | @Retention(RetentionPolicy.RUNTIME) 42 | @Target({ElementType.METHOD}) 43 | @interface ToggleTesting { 44 | TestExperimentName[] treated(); 45 | } 46 | 47 | private XPTest experimentation; 48 | 49 | private boolean tBool = false; 50 | 51 | public void conditional_contains_stale_flag() { 52 | // BUG: Diagnostic contains: Cleans stale XP flags 53 | if (experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG)) { 54 | System.out.println("Hello World"); 55 | } 56 | } 57 | 58 | public void conditional_with_else_contains_stale_flag() { 59 | // BUG: Diagnostic contains: Cleans stale XP flags 60 | if (experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG)) { 61 | System.out.println("Hello World"); 62 | } else { 63 | System.out.println("Hi world"); 64 | } 65 | } 66 | 67 | public void complex_conditional_contains_stale_flag() { 68 | // BUG: Diagnostic contains: Cleans stale XP flags 69 | if (true || (tBool && experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG))) { 70 | System.out.println("Hello World"); 71 | } else { 72 | System.out.println("Hi world"); 73 | } 74 | } 75 | 76 | public void other_api_stale_flag() { 77 | // BUG: Diagnostic contains: Cleans stale XP flags 78 | if (experimentation.isFlagTreated(TestExperimentName.STALE_FLAG)) { 79 | System.out.println("Hello World"); 80 | } else { 81 | System.out.println("Hi world"); 82 | } 83 | } 84 | 85 | public void assignments_containing_stale_flag() { 86 | // BUG: Diagnostic contains: Cleans stale XP flags 87 | tBool = experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG); 88 | 89 | // BUG: Diagnostic contains: Cleans stale XP flags 90 | tBool = experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG) && true; 91 | 92 | // BUG: Diagnostic contains: Cleans stale XP flags 93 | tBool = experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG) || true; 94 | 95 | // BUG: Diagnostic contains: Cleans stale XP flags 96 | tBool = experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG) || tBool; 97 | 98 | // BUG: Diagnostic contains: Cleans stale XP flags 99 | tBool = experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG) && (tBool || true); 100 | } 101 | 102 | public boolean return_contains_stale_flag() { 103 | // BUG: Diagnostic contains: Cleans stale XP flags 104 | return experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG); 105 | } 106 | 107 | public void condexp_contains_stale_flag() { 108 | // BUG: Diagnostic contains: Cleans stale XP flags 109 | tBool = experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG) ? true : false; 110 | } 111 | 112 | public void misc_xp_apis_containing_stale_flag() { 113 | // BUG: Diagnostic contains: Cleans stale XP flags 114 | if (experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG) && (tBool || true)) {} 115 | 116 | // BUG: Diagnostic contains: Cleans stale XP flags 117 | experimentation.putToggleEnabled(TestExperimentName.STALE_FLAG); 118 | 119 | // BUG: Diagnostic contains: Cleans stale XP flags 120 | experimentation.includeEvent(TestExperimentName.STALE_FLAG); 121 | 122 | // BUG: Diagnostic contains: Cleans stale XP flags 123 | experimentation.putToggleDisabled(TestExperimentName.STALE_FLAG); 124 | 125 | // BUG: Diagnostic contains: Cleans stale XP flags 126 | if (experimentation.isToggleDisabled(TestExperimentName.STALE_FLAG) && (tBool || true)) {} 127 | } 128 | 129 | public int return_within_if_basic() { 130 | // BUG: Diagnostic contains: Cleans stale XP flags 131 | if (experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG)) { 132 | return 20; 133 | } 134 | return 30; 135 | } 136 | 137 | public int return_within_if_additional(int x) { 138 | if (x == 0) { 139 | // BUG: Diagnostic contains: Cleans stale XP flags 140 | if (experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG)) { 141 | return 0; 142 | } 143 | return 75; 144 | } 145 | 146 | if (x == 1) 147 | // BUG: Diagnostic contains: Cleans stale XP flags 148 | if (experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG)) { 149 | return 1; 150 | } else { 151 | return 76; 152 | } 153 | 154 | if (x == 2) { 155 | int y = 3; 156 | // BUG: Diagnostic contains: Cleans stale XP flags 157 | if (experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG)) { 158 | y++; 159 | return y; 160 | } 161 | return y + 10; 162 | } 163 | 164 | if (x == 3) { 165 | int z = 4; 166 | // BUG: Diagnostic contains: Cleans stale XP flags 167 | if (experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG)) { 168 | z++; 169 | } else { 170 | z = z * 5; 171 | return z + 10; 172 | } 173 | return z; 174 | } 175 | 176 | return 100; 177 | } 178 | 179 | @ToggleTesting(treated = TestExperimentName.STALE_FLAG) 180 | // BUG: Diagnostic contains: Cleans stale XP flags 181 | public void annotation_test() {} 182 | 183 | @Inject XPTest injectedExperimentsShouldBeDeleted; 184 | 185 | private int testRemovingInjectField() { 186 | // BUG: Diagnostic contains: Cleans stale XP flags 187 | if (injectedExperimentsShouldBeDeleted.isToggleEnabled(TestExperimentName.STALE_FLAG)) return 1; 188 | else return 2; 189 | } 190 | 191 | @Inject XPTest injectedExperimentsMultipleUses; 192 | 193 | private void randomSet(XPTest x) { 194 | injectedExperimentsMultipleUses = x; 195 | } 196 | 197 | private int testNotRemovingInjectField() { 198 | // BUG: Diagnostic contains: Cleans stale XP flags 199 | if (injectedExperimentsMultipleUses.isToggleEnabled(TestExperimentName.STALE_FLAG)) return 1; 200 | else return 2; 201 | } 202 | 203 | @Provides 204 | public int unusedParamTestWithDeletion(XPTest x) { 205 | // BUG: Diagnostic contains: Cleans stale XP flags 206 | if (x.isToggleEnabled(TestExperimentName.STALE_FLAG)) return 1; 207 | else return 2; 208 | } 209 | 210 | @Provides 211 | public int unusedParamTestWithoutDeletion(XPTest x) { 212 | 213 | if (x != null) { 214 | // just another use to prevent deletion of this parameter. 215 | } 216 | 217 | // BUG: Diagnostic contains: Cleans stale XP flags 218 | if (x.isToggleEnabled(TestExperimentName.STALE_FLAG)) return 1; 219 | else return 2; 220 | } 221 | 222 | private void testMultipleCalls(int x) { 223 | if (x > 0) { 224 | // BUG: Diagnostic contains: Cleans stale XP flags 225 | experimentation.includeEvent(TestExperimentName.STALE_FLAG); 226 | // BUG: Diagnostic contains: Cleans stale XP flags 227 | if (experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG)) { 228 | // comment0 229 | return; 230 | } else { 231 | // comment1 232 | return; 233 | } 234 | } 235 | 236 | // do something here 237 | return; 238 | } 239 | 240 | public int or_compounded_with_not(int x, boolean extra_toggle) { 241 | // BUG: Diagnostic contains: Cleans stale XP flags 242 | if (extra_toggle || !experimentation.isToggleDisabled(TestExperimentName.STALE_FLAG)) { 243 | return 0; 244 | } else { 245 | return 1; 246 | } 247 | } 248 | 249 | public int remove_else_if(boolean extra_toggle) { 250 | // BUG: Diagnostic contains: Cleans stale XP flags 251 | if (extra_toggle) { 252 | return 0; 253 | } else if (experimentation.isToggleDisabled(TestExperimentName.STALE_FLAG)) { 254 | return 1; 255 | } else { 256 | return 2; 257 | } 258 | } 259 | 260 | class XPTest { 261 | public boolean isToggleEnabled(TestExperimentName x) { 262 | return true; 263 | } 264 | 265 | public boolean putToggleEnabled(TestExperimentName x) { 266 | return true; 267 | } 268 | 269 | public boolean includeEvent(TestExperimentName x) { 270 | return true; 271 | } 272 | 273 | public boolean isToggleDisabled(TestExperimentName x) { 274 | return true; 275 | } 276 | 277 | public boolean putToggleDisabled(TestExperimentName x) { 278 | return true; 279 | } 280 | 281 | public boolean isFlagTreated(TestExperimentName x) { 282 | return true; 283 | } 284 | 285 | public boolean isToggleInGroup(TestExperimentName x) { 286 | return true; 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /swift/tests/treated.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // swiftlint:disable custom_rules 18 | 19 | import Foundation 20 | 21 | protocol ExperimentKeying { 22 | var asString: String { get } 23 | } 24 | 25 | enum ExperimentNamesSwift: String, ExperimentKeying { 26 | //comment0 27 | case random_flag //comment1 28 | 29 | //comment4 30 | case test_experiment_suffix //comment5 31 | case test_second_experiment 32 | 33 | var asString: String { 34 | return String(describing: self) 35 | } 36 | } 37 | 38 | protocol TreatmentGroupKeying { 39 | var asString: String { get } 40 | } 41 | 42 | protocol ParamManager { 43 | func getParam(String param: String, defaultValue: Int) -> Bool 44 | } 45 | 46 | extension ParamManager { 47 | func getParam(String param: paramString, defaultValue: Int) -> Bool { 48 | return true 49 | } 50 | } 51 | 52 | protocol CachedExperimenting { 53 | func isInControlGroup(forExperiment experimentKey: ExperimentKeying) -> Bool 54 | func isTreated(forExperiment experimentKey: ExperimentKeying) -> Bool 55 | func addTreatedExperiment(forExperiment experimentKey: ExperimentKeying) -> Bool 56 | func removeTreatedExperiment(forExperiment experimentKey: ExperimentKeying) -> Bool 57 | func isInTreatmentGroup(treatmentGroup treatmentGroupKey: TreatmentGroupKeying, forExperiment experimentKey: ExperimentKeying) -> Bool 58 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: String) -> String 59 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Int) -> Int 60 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Float) -> Float 61 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Double) -> Double 62 | func sendInclusionEvent(forExperiment experimentKey: ExperimentKeying, treatmentGroup treatmentGroupKey: TreatmentGroupKeying) 63 | } 64 | 65 | extension CachedExperimenting { 66 | 67 | func isInControlGroup(forExperiment experimentKey: ExperimentKeying) -> Bool { 68 | return true 69 | } 70 | 71 | func isTreated(forExperiment experimentKey: ExperimentKeying) -> Bool { 72 | return true 73 | } 74 | 75 | func isInTreatmentGroup(treatmentGroup treatmentGroupKey: TreatmentGroupKeying, forExperiment experimentKey: ExperimentKeying) -> Bool { 76 | return true 77 | } 78 | 79 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: String) -> String { 80 | return "foobar" 81 | } 82 | 83 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Int) -> Int { 84 | return 42 85 | } 86 | 87 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Float) -> Float { 88 | return 42.0 89 | } 90 | 91 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Double) -> Double { 92 | return 42.0 93 | } 94 | 95 | func sendInclusionEvent(forExperiment experimentKey: ExperimentKeying, treatmentGroup treatmentGroupKey: TreatmentGroupKeying) { 96 | } 97 | } 98 | 99 | class CachedExperiments: CachedExperimenting { 100 | 101 | init() { 102 | } 103 | 104 | } 105 | 106 | class SwiftExamples { 107 | 108 | private let impressionStr: String 109 | 110 | let cachedExperiments = CachedExperiments() 111 | 112 | public enum test_12experiment: String { 113 | case delay 114 | } 115 | 116 | let p1 = "p1" 117 | static var p2 = "p2" 118 | 119 | private let fieldZ: Bool 120 | 121 | func test_expressions() { 122 | print("treated") 123 | 124 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment_suffix) { 125 | print("treated") 126 | } 127 | 128 | let x = false 129 | var y = false 130 | print("test 1") 131 | 132 | if x { 133 | print("test 2") 134 | } 135 | 136 | if x { 137 | print("test 3") 138 | } 139 | 140 | if (x || y) { 141 | print("test 4") 142 | } 143 | 144 | if (x || y) { 145 | print("test 5") 146 | } 147 | 148 | if (x && y) { 149 | print("test 6") 150 | } 151 | 152 | if (x && y) { 153 | print("test 7") 154 | } 155 | 156 | if x, y { 157 | print("test 8") 158 | } 159 | 160 | if y == x { 161 | print("test 9") 162 | } 163 | 164 | if y == x { 165 | print("test 10") 166 | } 167 | 168 | print("test 11") 169 | 170 | if x && y { 171 | print("test 12") 172 | } 173 | 174 | y = true 175 | y = x 176 | y = true 177 | 178 | print("treated") 179 | 180 | while true { 181 | 182 | } 183 | 184 | var xyz = getParam("hello") 185 | 186 | let abc = "world" 187 | 188 | xyz = getParam(abc) 189 | getParam(p1) 190 | getParam(p2) 191 | getParam(xyz) 192 | } 193 | 194 | func addTreatedExperiment(forExperiment experimentKey: ExperimentKeying) -> Bool { 195 | return true 196 | 197 | } 198 | 199 | func test_additional() -> Bool { 200 | 201 | var platformUIChange = true 202 | var recordMode = true 203 | recordMode = false || platformUIChange 204 | 205 | recordMode = recordMode 206 | recordMode = platformUIChange 207 | 208 | recordMode = platformUIChange == recordMode ? platformUIChange : recordMode 209 | print("treated") 210 | print("treated") 211 | return 212 | } 213 | 214 | func test_closure() -> Bool { 215 | 216 | cachedExperimentingMock.isTreatedHandler = { (key: ExperimentKeying) in 217 | return key.asString == ExperimentNamesSwift.test_second_experiment.asString 218 | } 219 | return true 220 | 221 | } 222 | 223 | // test for T2205641 224 | func test_experimentParameter() -> Bool { 225 | return true 226 | } 227 | 228 | func test_T2206585() -> Bool { 229 | 230 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) 231 | && self.impressionStr != nil { 232 | print("br1") 233 | } else { 234 | print("br2") 235 | } 236 | 237 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesRewards.test_second_experiment) 238 | && self.impressionStr != nil { 239 | print("br3") 240 | } else { 241 | print("br4") 242 | } 243 | 244 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) 245 | && self.impressionStr != nil { 246 | print("br5") 247 | } else { 248 | print("br6") 249 | } 250 | 251 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) 252 | && self.impressionStr != nil 253 | && self.impressionStr == "abcd" { 254 | print("br7") 255 | } else { 256 | print("br8") 257 | } 258 | 259 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) 260 | || (self.impressionStr != nil) { 261 | print("br9") 262 | } else { 263 | print("br10") 264 | } 265 | 266 | return true 267 | 268 | } 269 | 270 | // tests for task T2191251 271 | func storeuse_before() { 272 | print("Hi world") 273 | print("somestring0") 274 | print("somestring1") 275 | print("randomstring") 276 | print("somestring3") 277 | print("somestring5") 278 | 279 | if fieldZ { 280 | print("somestring6") 281 | } 282 | 283 | if self.fieldZ { 284 | print("fieldXfieldYfieldZ") 285 | } 286 | 287 | if fieldZ { 288 | print("pqr") 289 | } 290 | } 291 | 292 | func storeuse_init() { 293 | fieldZ = cachedexperiments.isTreated(forexperiment: ExperimentNamesSwift.test_experiment2) 294 | print("Hello world") 295 | print("Hi world") 296 | } 297 | 298 | func storeuse_after() { 299 | print("Hi world") 300 | print("somestring0") 301 | print("somestring1") 302 | print("randomstring") 303 | print("somestring3") 304 | print("somestring5") 305 | 306 | if fieldZ { 307 | print("somestring6") 308 | } 309 | 310 | if self.fieldZ { 311 | print("XYZ") 312 | } 313 | 314 | if fieldZ { 315 | print("pqr") 316 | } 317 | } 318 | 319 | func test_nilcoalescing() { 320 | print("treated1") 321 | print("control2") 322 | print("control3") 323 | print("treated4") 324 | print("treated5") 325 | print("control6") 326 | 327 | if cachedExperiments?.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) ?? false { 328 | print("treated1") 329 | } else { 330 | print("control1") 331 | } 332 | 333 | var v = true 334 | v = false 335 | 336 | v = cachedExperiments?.isInControlGroup(forExperiment: ExperimentNamesSwift.test_second_experiment) ?? false 337 | } 338 | 339 | private let conj1: Bool 340 | private let conj2: Bool 341 | private let conj3: Bool 342 | 343 | func test_T2282603() { 344 | self.conj1 = cachedexperiments.isInControlGroup(forexperiment: ExperimentNamesSwift.test_second_experiment) 345 | self.conj2 = cachedexperiments.isInControlGroup(forexperiment: ExperimentNamesSwift.test_experiment_suffix) 346 | self.conj3 = true 347 | } 348 | 349 | // Test for T2606011 350 | private var shouldDoSomething: Bool { 351 | return true 352 | } 353 | 354 | private let engineeringFlags: [ExperimentNamesLoyalty] = [ 355 | .loyalty_credits_purchase_selection_rib_refactor, 356 | .loyalty_card_banner_impression_fix, 357 | .loyalty_credits_purchase_selection_default_payment_profile_fix, 358 | .loyalty_credits_purchase_addon_explicit_layout, 359 | .loyalty_stack_view_migration 360 | ] 361 | } 362 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner]. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /swift/tests/testfile.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // swiftlint:disable custom_rules 18 | 19 | import Foundation 20 | 21 | protocol ExperimentKeying { 22 | var asString: String { get } 23 | } 24 | 25 | enum ExperimentNamesSwift: String, ExperimentKeying { 26 | //comment0 27 | case random_flag //comment1 28 | 29 | ///comment2 30 | case test_experiment // comment3 31 | 32 | //comment4 33 | case test_experiment_suffix //comment5 34 | case test_second_experiment 35 | 36 | var asString: String { 37 | return String(describing: self) 38 | } 39 | } 40 | 41 | protocol TreatmentGroupKeying { 42 | var asString: String { get } 43 | } 44 | 45 | protocol ParamManager { 46 | func getParam(String param: String, defaultValue: Int) -> Bool 47 | } 48 | 49 | extension ParamManager { 50 | func getParam(String param: paramString, defaultValue: Int) -> Bool { 51 | return true 52 | } 53 | } 54 | 55 | protocol CachedExperimenting { 56 | func isInControlGroup(forExperiment experimentKey: ExperimentKeying) -> Bool 57 | func isTreated(forExperiment experimentKey: ExperimentKeying) -> Bool 58 | func addTreatedExperiment(forExperiment experimentKey: ExperimentKeying) -> Bool 59 | func removeTreatedExperiment(forExperiment experimentKey: ExperimentKeying) -> Bool 60 | func isInTreatmentGroup(treatmentGroup treatmentGroupKey: TreatmentGroupKeying, forExperiment experimentKey: ExperimentKeying) -> Bool 61 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: String) -> String 62 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Int) -> Int 63 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Float) -> Float 64 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Double) -> Double 65 | func sendInclusionEvent(forExperiment experimentKey: ExperimentKeying, treatmentGroup treatmentGroupKey: TreatmentGroupKeying) 66 | } 67 | 68 | extension CachedExperimenting { 69 | 70 | func isInControlGroup(forExperiment experimentKey: ExperimentKeying) -> Bool { 71 | return true 72 | } 73 | 74 | func isTreated(forExperiment experimentKey: ExperimentKeying) -> Bool { 75 | return true 76 | } 77 | 78 | func isInTreatmentGroup(treatmentGroup treatmentGroupKey: TreatmentGroupKeying, forExperiment experimentKey: ExperimentKeying) -> Bool { 79 | return true 80 | } 81 | 82 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: String) -> String { 83 | return "foobar" 84 | } 85 | 86 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Int) -> Int { 87 | return 42 88 | } 89 | 90 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Float) -> Float { 91 | return 42.0 92 | } 93 | 94 | func experimentParameter(name parameterName: String, forExperiment experimentKey: ExperimentKeying, defaultValue: Double) -> Double { 95 | return 42.0 96 | } 97 | 98 | func sendInclusionEvent(forExperiment experimentKey: ExperimentKeying, treatmentGroup treatmentGroupKey: TreatmentGroupKeying) { 99 | } 100 | } 101 | 102 | class CachedExperiments: CachedExperimenting { 103 | 104 | init() { 105 | } 106 | 107 | } 108 | 109 | class SwiftExamples { 110 | 111 | private let impressionStr: String 112 | 113 | let cachedExperiments = CachedExperiments() 114 | 115 | public enum test_experiment: String, TreatmentGroupKeying { 116 | case delay 117 | } 118 | 119 | public enum test_12experiment: String { 120 | case delay 121 | } 122 | 123 | let p1 = "p1" 124 | static var p2 = "p2" 125 | 126 | let fieldX: Bool 127 | private let fieldY: Bool 128 | private let fieldZ: Bool 129 | 130 | func test_expressions() { 131 | 132 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) { 133 | print("treated") 134 | } 135 | 136 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment_suffix) { 137 | print("treated") 138 | } 139 | 140 | if !cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) { 141 | print("not treated / control") 142 | } 143 | 144 | let x = false 145 | var y = false 146 | 147 | 148 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) || x { 149 | print("test 1") 150 | } 151 | 152 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) && x { 153 | print("test 2") 154 | } 155 | 156 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment), x { 157 | print("test 3") 158 | } 159 | 160 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) && (x || y) { 161 | print("test 4") 162 | } 163 | 164 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment), (x || y) { 165 | print("test 5") 166 | } 167 | 168 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) && (x && y) { 169 | print("test 6") 170 | } 171 | 172 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment), (x && y) { 173 | print("test 7") 174 | } 175 | 176 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment), x, y { 177 | print("test 8") 178 | } 179 | 180 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) && y == x { 181 | print("test 9") 182 | } 183 | 184 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment), y == x { 185 | print("test 10") 186 | } 187 | 188 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) || x || y { 189 | print("test 11") 190 | } 191 | 192 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) && x && y { 193 | print("test 12") 194 | } 195 | 196 | y = cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) 197 | y = cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) && x 198 | y = cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) || x 199 | 200 | guard cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) else { 201 | print("not treated / control") 202 | return 203 | } 204 | 205 | print("treated") 206 | 207 | while true { 208 | 209 | } 210 | 211 | var xyz = getParam("hello") 212 | 213 | let abc = "world" 214 | 215 | xyz = getParam(abc) 216 | getParam(p1) 217 | getParam(p2) 218 | getParam(xyz) 219 | } 220 | 221 | 222 | func addTreatedExperiment(forExperiment experimentKey: ExperimentKeying) -> Bool { 223 | return true 224 | 225 | } 226 | 227 | func test_additional() -> Bool { 228 | 229 | var platformUIChange = true 230 | var recordMode = true 231 | recordMode = false || platformUIChange 232 | 233 | recordMode = cachedExperiments.isInControlGroup(forExperiment: ExperimentNamesSwift.test_experiment) ? platformUIChange : recordMode 234 | recordMode = cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) ? platformUIChange : recordMode 235 | 236 | recordMode = platformUIChange == recordMode ? platformUIChange : recordMode 237 | 238 | if cachedExperiments.isInControlGroup(forExperiment: ExperimentNamesSwift.test_experiment) { 239 | print("not treated / control") 240 | } 241 | 242 | if !cachedExperiments.isInControlGroup(forExperiment: ExperimentNamesSwift.test_experiment) { 243 | print("treated") 244 | } 245 | 246 | guard cachedExperiments.isInControlGroup(forExperiment: ExperimentNamesSwift.test_experiment) else { 247 | print("treated") 248 | return 249 | } 250 | 251 | if recordMode { 252 | addTreatedExperiment(forExperiment: ExperimentNamesSwift.test_experiment) 253 | } else { 254 | cachedExperiments.removeTreatedExperiment(forExperiment: ExperimentNamesSwift.test_experiment) 255 | 256 | } 257 | 258 | print("not treated / control") 259 | 260 | return false && recordMode == platformUIChange 261 | } 262 | 263 | func test_closure() -> Bool { 264 | cachedExperimentingMock.isTreatedHandler = { (key: ExperimentKeying) in 265 | return key.asString == ExperimentNamesSwift.test_experiment.asString 266 | } 267 | 268 | cachedExperimentingMock.isTreatedHandler = { (key: ExperimentKeying) in 269 | return key.asString == ExperimentNamesSwift.test_second_experiment.asString 270 | } 271 | 272 | cachedExperimentingMock.isTreatedHandler2 = { (key: ExperimentKeying) in 273 | return key.asString == ExperimentNamesSwift.test_experiment.asString || 274 | key.asString == ExperimentNamesSwift.test_second_experiment.asString 275 | 276 | } 277 | 278 | cachedExperiments.isTreatedHandler = { (_ experiment: ExperimentKeying) in 279 | if experiment.asString == ExperimentNamesSwift.test_experiment.asString { 280 | return true 281 | } 282 | return false 283 | } 284 | return true 285 | 286 | } 287 | 288 | // test for T2205641 289 | func test_experimentParameter() -> Bool { 290 | cachedExperiments.experimentParameter("abcd", ExperimentNamesSwift.test_experiment, "efgh") 291 | self.impressionStr = cachedExperiments.experimentParameter("abcd", ExperimentNamesSwift.test_experiment, "efgh") 292 | return true 293 | } 294 | 295 | func test_T2206585() -> Bool { 296 | 297 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) 298 | && cachedExperiments.isTreated(forExperiment: ExperimentNamesRewards.test_experiment) 299 | && self.impressionStr != nil { 300 | print("br1") 301 | } else { 302 | print("br2") 303 | } 304 | 305 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) 306 | && cachedExperiments.isTreated(forExperiment: ExperimentNamesRewards.test_second_experiment) 307 | && self.impressionStr != nil { 308 | print("br3") 309 | } else { 310 | print("br4") 311 | } 312 | 313 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) 314 | && self.impressionStr != nil 315 | && cachedExperiments.isTreated(forExperiment: ExperimentNamesRewards.test_experiment) { 316 | print("br5") 317 | } else { 318 | print("br6") 319 | } 320 | 321 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) 322 | && self.impressionStr != nil 323 | && self.impressionStr == "abcd" 324 | && cachedExperiments.isTreated(forExperiment: ExperimentNamesRewards.test_experiment) { 325 | print("br7") 326 | } else { 327 | print("br8") 328 | } 329 | 330 | if cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) 331 | || (cachedExperiments.isTreated(forExperiment: ExperimentNamesRewards.test_experiment) 332 | && self.impressionStr != nil) { 333 | print("br9") 334 | } else { 335 | print("br10") 336 | } 337 | 338 | return true 339 | 340 | } 341 | 342 | // tests for task T2191251 343 | func storeuse_before() { 344 | if self.fieldX { 345 | print("Hi world") 346 | } 347 | 348 | if fieldX { 349 | print("somestring0") 350 | } 351 | 352 | if fieldY { 353 | print("somestring1") 354 | } 355 | 356 | if self.fieldY { 357 | print("randomstring") 358 | } 359 | 360 | if !fieldY { 361 | print("somestring2") 362 | } else { 363 | print("somestring3") 364 | } 365 | 366 | if !self.fieldY { 367 | print("somestring4") 368 | } else { 369 | print("somestring5") 370 | } 371 | 372 | if self.fieldY && fieldZ { 373 | print("somestring6") 374 | } 375 | 376 | if self.fieldZ { 377 | print("fieldXfieldYfieldZ") 378 | } 379 | 380 | if fieldZ { 381 | print("pqr") 382 | } 383 | } 384 | 385 | func storeuse_init() { 386 | 387 | self.fieldX = cachedexperiments.isTreated(forexperiment: ExperimentNamesSwift.test_experiment) 388 | fieldY = cachedexperiments.isTreated(forexperiment: ExperimentNamesSwift.test_experiment) 389 | fieldZ = cachedexperiments.isTreated(forexperiment: ExperimentNamesSwift.test_experiment2) 390 | 391 | if self.fieldX { 392 | print("Hello world") 393 | } 394 | 395 | if self.fieldY { 396 | print("Hi world") 397 | } 398 | } 399 | 400 | func storeuse_after() { 401 | if self.fieldX { 402 | print("Hi world") 403 | } 404 | 405 | if fieldX { 406 | print("somestring0") 407 | } 408 | 409 | if fieldY { 410 | print("somestring1") 411 | } 412 | 413 | if self.fieldY { 414 | print("randomstring") 415 | } 416 | 417 | if !fieldY { 418 | print("somestring2") 419 | } else { 420 | print("somestring3") 421 | } 422 | 423 | if !self.fieldY { 424 | print("somestring4") 425 | } else { 426 | print("somestring5") 427 | } 428 | 429 | if self.fieldY && fieldZ { 430 | print("somestring6") 431 | } 432 | 433 | if self.fieldZ { 434 | print("XYZ") 435 | } 436 | 437 | if fieldZ { 438 | print("pqr") 439 | } 440 | } 441 | 442 | func test_nilcoalescing() { 443 | 444 | if cachedExperiments?.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) ?? false { 445 | print("treated1") 446 | } else { 447 | print("control1") 448 | } 449 | 450 | if cachedExperiments?.isInControlGroup(forExperiment: ExperimentNamesSwift.test_experiment) ?? false { 451 | print("treated2") 452 | } else { 453 | print("control2") 454 | } 455 | 456 | if (!cachedExperiments?.isTreated(forExperiment: ExperimentNamesSwift.test_experiment)) ?? false { 457 | print("treated3") 458 | } else { 459 | print("control3") 460 | } 461 | 462 | if !(cachedExperiments?.isInControlGroup(forExperiment: ExperimentNamesSwift.test_experiment)) ?? false { 463 | print("treated4") 464 | } else { 465 | print("control4") 466 | } 467 | 468 | if cachedExperiments?.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) ?? (cachedExperiments?.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) ?? false) { 469 | print("treated5") 470 | } else { 471 | print("control5") 472 | } 473 | 474 | if !cachedExperiments?.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) ?? (cachedExperiments?.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) ?? false) { 475 | print("treated6") 476 | } else { 477 | print("control6") 478 | } 479 | 480 | if cachedExperiments?.isTreated(forExperiment: ExperimentNamesSwift.test_second_experiment) ?? false { 481 | print("treated1") 482 | } else { 483 | print("control1") 484 | } 485 | 486 | var v = cachedExperiments?.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) ?? false 487 | v = cachedExperiments?.isInControlGroup(forExperiment: ExperimentNamesSwift.test_experiment) ?? false 488 | 489 | v = cachedExperiments?.isInControlGroup(forExperiment: ExperimentNamesSwift.test_second_experiment) ?? false 490 | } 491 | 492 | private let conj1: Bool 493 | private let conj2: Bool 494 | private let conj3: Bool 495 | 496 | func test_T2282603() { 497 | self.conj1 = cachedexperiments.isInControlGroup(forexperiment: ExperimentNamesSwift.test_second_experiment) 498 | self.conj2 = cachedexperiments.isInControlGroup(forexperiment: ExperimentNamesSwift.test_experiment_suffix) && 499 | cachedexperiments.isTreated(forexperiment: ExperimentNamesSwift.test_experiment) 500 | 501 | self.conj3 = cachedexperiments.isTreated(forexperiment: ExperimentNamesSwift.test_experiment) || cachedexperiments.isInControlGroup(forexperiment: ExperimentNamesSwift.test_experiment_suffix) 502 | } 503 | 504 | // Test for T2606011 505 | private var shouldDoSomething: Bool { 506 | return cachedExperiments.isTreated(forExperiment: ExperimentNamesSwift.test_experiment) 507 | } 508 | 509 | private let engineeringFlags: [ExperimentNamesLoyalty] = [ 510 | .loyalty_credits_purchase_selection_rib_refactor, 511 | .loyalty_card_banner_impression_fix, 512 | .loyalty_credits_purchase_selection_default_payment_profile_fix, 513 | .test_experiment, 514 | .loyalty_credits_purchase_addon_explicit_layout, 515 | .loyalty_stack_view_migration 516 | ] 517 | } 518 | -------------------------------------------------------------------------------- /java/piranha/src/test/java/com/uber/piranha/XPFlagCleanerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Uber Technologies, Inc. 3 | * 4 | *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. You may obtain a copy of the License at 6 | * 7 | *

http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | *

Unless required by applicable law or agreed to in writing, software distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.uber.piranha; 15 | 16 | import static com.google.common.truth.Truth.assert_; 17 | 18 | import com.google.errorprone.BugCheckerRefactoringTestHelper; 19 | import com.google.errorprone.CompilationTestHelper; 20 | import com.google.errorprone.ErrorProneFlags; 21 | import java.io.IOException; 22 | import java.text.ParseException; 23 | import java.util.Arrays; 24 | import org.junit.Before; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.rules.TemporaryFolder; 28 | import org.junit.runner.RunWith; 29 | import org.junit.runners.JUnit4; 30 | 31 | @RunWith(JUnit4.class) 32 | public class XPFlagCleanerTest { 33 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 34 | 35 | private CompilationTestHelper compilationHelper; 36 | 37 | @Before 38 | public void setup() { 39 | compilationHelper = CompilationTestHelper.newInstance(XPFlagCleaner.class, getClass()); 40 | compilationHelper.setArgs(Arrays.asList("-d", temporaryFolder.getRoot().getAbsolutePath())); 41 | } 42 | 43 | @Test 44 | public void test_xpflagsPositiveCases() { 45 | compilationHelper.setArgs( 46 | Arrays.asList( 47 | "-d", 48 | temporaryFolder.getRoot().getAbsolutePath(), 49 | "-XepOpt:Piranha:FlagName=STALE_FLAG", 50 | "-XepOpt:Piranha:IsTreated=true", 51 | "-XepOpt:Piranha:Config=config/piranha.properties")); 52 | compilationHelper.addSourceFile("XPFlagCleanerPositiveCases.java").doTest(); 53 | } 54 | 55 | @Test 56 | public void test_xpflagsNegativeCases() { 57 | compilationHelper.setArgs( 58 | Arrays.asList( 59 | "-d", 60 | temporaryFolder.getRoot().getAbsolutePath(), 61 | "-XepOpt:Piranha:FlagName=STALE_FLAG", 62 | "-XepOpt:Piranha:IsTreated=true", 63 | "-XepOpt:Piranha:Config=config/piranha.properties")); 64 | compilationHelper.addSourceFile("XPFlagCleanerNegativeCases.java").doTest(); 65 | } 66 | 67 | @Test 68 | public void positiveTreatment() throws IOException { 69 | 70 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 71 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 72 | b.putFlag("Piranha:IsTreated", "true"); 73 | b.putFlag("Piranha:Config", "config/piranha.properties"); 74 | 75 | try { 76 | BugCheckerRefactoringTestHelper bcr = 77 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 78 | 79 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 80 | 81 | BugCheckerRefactoringTestHelper.ExpectOutput eo = 82 | bcr.addInput("XPFlagCleanerPositiveCases.java"); 83 | eo.addOutput("XPFlagCleanerPositiveCasesTreatment.java"); 84 | 85 | bcr.doTest(); 86 | } catch (ParseException pe) { 87 | pe.printStackTrace(); 88 | assert_().fail("Incorrect parameters passed to the checker"); 89 | } 90 | } 91 | 92 | @Test 93 | public void positiveSpecificTreatmentGroup() throws IOException { 94 | 95 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 96 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 97 | b.putFlag("Piranha:IsTreated", "true"); 98 | b.putFlag("Piranha:TreatmentGroup", "GROUP_A"); 99 | b.putFlag("Piranha:Config", "config/piranha.properties"); 100 | 101 | try { 102 | BugCheckerRefactoringTestHelper bcr = 103 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 104 | 105 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 106 | 107 | bcr = addHelperClasses(bcr); 108 | 109 | bcr.addInputLines( 110 | "TestExperimentName.java", 111 | "package com.uber.piranha;", 112 | "public enum TestExperimentName {", 113 | " STALE_FLAG", 114 | "}") 115 | .addOutputLines( 116 | "TestExperimentName.java", 117 | "package com.uber.piranha;", 118 | "public enum TestExperimentName {", // Ideally we would remove this too, fix later 119 | "}") 120 | .addInputLines( 121 | "TestExperimentGroups.java", 122 | "package com.uber.piranha;", 123 | "public enum TestExperimentGroups {", 124 | " GROUP_A,", 125 | " GROUP_B,", 126 | "}") 127 | .addOutputLines( 128 | "TestExperimentGroups.java", 129 | "package com.uber.piranha;", 130 | "//[PIRANHA_DELETE_FILE_SEQ] Delete this class if not automatically removed.", 131 | "enum TestExperimentGroups { }") 132 | .addInputLines( 133 | "XPFlagCleanerSinglePositiveCase.java", 134 | "package com.uber.piranha;", 135 | "import static com.uber.piranha.TestExperimentName.STALE_FLAG;", 136 | "import static com.uber.piranha.TestExperimentGroups.GROUP_A;", 137 | "import static com.uber.piranha.TestExperimentGroups.GROUP_B;", 138 | "class XPFlagCleanerSinglePositiveCase {", 139 | " private XPTest experimentation;", 140 | " public String groupToString() {", 141 | " // BUG: Diagnostic contains: Cleans stale XP flags", 142 | " if (experimentation.isToggleDisabled(STALE_FLAG)) { return \"\"; }", 143 | " else if (experimentation.isToggleInGroup(", 144 | " STALE_FLAG,GROUP_A)) { ", 145 | " return \"A\";", 146 | " } else if (experimentation.isToggleInGroup(", 147 | " STALE_FLAG,GROUP_B)) { ", 148 | " return \"B\";", 149 | " } else { return \"C\"; }", 150 | " }", 151 | "}") 152 | .addOutputLines( 153 | "XPFlagCleanerSinglePositiveCase.java", 154 | "package com.uber.piranha;", 155 | "class XPFlagCleanerSinglePositiveCase {", 156 | " private XPTest experimentation;", 157 | " public String groupToString() {", 158 | " return \"A\";", 159 | " }", 160 | "}"); 161 | 162 | bcr.doTest(); 163 | } catch (ParseException pe) { 164 | pe.printStackTrace(); 165 | assert_().fail("Incorrect parameters passed to the checker"); 166 | } 167 | } 168 | 169 | @Test 170 | public void dontRemoveGenericallyNamedTreatmentGroups() throws IOException { 171 | 172 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 173 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 174 | b.putFlag("Piranha:IsTreated", "true"); 175 | b.putFlag("Piranha:TreatmentGroup", "TREATED"); 176 | b.putFlag("Piranha:Config", "config/piranha.properties"); 177 | 178 | try { 179 | BugCheckerRefactoringTestHelper bcr = 180 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 181 | 182 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 183 | 184 | bcr = addHelperClasses(bcr); 185 | 186 | bcr.addInputLines( 187 | "TestExperimentName.java", 188 | "package com.uber.piranha;", 189 | "public enum TestExperimentName {", 190 | " STALE_FLAG", 191 | "}") 192 | .addOutputLines( 193 | "TestExperimentName.java", 194 | "package com.uber.piranha;", 195 | "public enum TestExperimentName {", // Ideally we would remove this too, fix later 196 | "}") 197 | .addInputLines( 198 | "TestExperimentGroups.java", 199 | "package com.uber.piranha;", 200 | "public enum TestExperimentGroups {", 201 | " TREATED,", 202 | " CONTROL,", 203 | "}") 204 | .addOutputLines( 205 | "TestExperimentGroups.java", 206 | "package com.uber.piranha;", 207 | "public enum TestExperimentGroups {", 208 | " TREATED,", 209 | " CONTROL,", 210 | "}") 211 | .addInputLines( 212 | "XPFlagCleanerSinglePositiveCase.java", 213 | "package com.uber.piranha;", 214 | "import static com.uber.piranha.TestExperimentName.STALE_FLAG;", 215 | "import static com.uber.piranha.TestExperimentGroups.TREATED;", 216 | "class XPFlagCleanerSinglePositiveCase {", 217 | " private XPTest experimentation;", 218 | " public String groupToString() {", 219 | " // BUG: Diagnostic contains: Cleans stale XP flags", 220 | " if (experimentation.isToggleDisabled(STALE_FLAG)) { return \"\"; }", 221 | " else if (experimentation.isToggleInGroup(", 222 | " STALE_FLAG,TREATED)) { ", 223 | " return \"Treated\";", 224 | " } else { return \"Controll\"; }", 225 | " }", 226 | "}") 227 | .addOutputLines( 228 | "XPFlagCleanerSinglePositiveCase.java", 229 | "package com.uber.piranha;", 230 | "import static com.uber.piranha.TestExperimentGroups.TREATED;", 231 | "class XPFlagCleanerSinglePositiveCase {", 232 | " private XPTest experimentation;", 233 | " public String groupToString() {", 234 | " return \"Treated\";", 235 | " }", 236 | "}"); 237 | 238 | bcr.doTest(); 239 | } catch (ParseException pe) { 240 | pe.printStackTrace(); 241 | assert_().fail("Incorrect parameters passed to the checker"); 242 | } 243 | } 244 | 245 | @Test 246 | public void positiveControl() throws IOException { 247 | 248 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 249 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 250 | b.putFlag("Piranha:IsTreated", "false"); 251 | b.putFlag("Piranha:Config", "config/piranha.properties"); 252 | 253 | try { 254 | BugCheckerRefactoringTestHelper bcr = 255 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 256 | 257 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 258 | 259 | BugCheckerRefactoringTestHelper.ExpectOutput eo = 260 | bcr.addInput("XPFlagCleanerPositiveCases.java"); 261 | eo.addOutput("XPFlagCleanerPositiveCasesControl.java"); 262 | 263 | bcr.doTest(); 264 | } catch (ParseException pe) { 265 | pe.printStackTrace(); 266 | assert_().fail("Incorrect parameters passed to the checker"); 267 | } 268 | } 269 | 270 | private BugCheckerRefactoringTestHelper addHelperClasses(BugCheckerRefactoringTestHelper bcr) 271 | throws IOException { 272 | return bcr.addInput("XPTest.java").expectUnchanged(); 273 | } 274 | 275 | @Test 276 | public void positiveRemoveImport() throws IOException { 277 | 278 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 279 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 280 | b.putFlag("Piranha:IsTreated", "true"); 281 | b.putFlag("Piranha:Config", "config/piranha.properties"); 282 | 283 | try { 284 | BugCheckerRefactoringTestHelper bcr = 285 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 286 | 287 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 288 | 289 | bcr = addHelperClasses(bcr); 290 | 291 | bcr.addInputLines( 292 | "TestExperimentName.java", 293 | "package com.uber.piranha;", 294 | "public enum TestExperimentName {", 295 | " STALE_FLAG", 296 | "}") 297 | .addOutputLines( 298 | "TestExperimentName.java", 299 | "package com.uber.piranha;", 300 | "public enum TestExperimentName {", // Ideally we would remove this too, fix later 301 | "}") 302 | .addInputLines( 303 | "XPFlagCleanerSinglePositiveCase.java", 304 | "package com.uber.piranha;", 305 | "import static com.uber.piranha.TestExperimentName" + ".STALE_FLAG;", 306 | "class XPFlagCleanerSinglePositiveCase {", 307 | " private XPTest experimentation;", 308 | " public boolean return_contains_stale_flag() {", 309 | " // BUG: Diagnostic contains: Cleans stale XP flags", 310 | " return experimentation.isToggleEnabled(TestExperimentName.STALE_FLAG);", 311 | " }", 312 | "}") 313 | .addOutputLines( 314 | "XPFlagCleanerSinglePositiveCase.java", 315 | "package com.uber.piranha;", 316 | "class XPFlagCleanerSinglePositiveCase {", 317 | " private XPTest experimentation;", 318 | " public boolean return_contains_stale_flag() {", 319 | " return true;", 320 | " }", 321 | "}"); 322 | 323 | bcr.doTest(); 324 | } catch (ParseException pe) { 325 | pe.printStackTrace(); 326 | assert_().fail("Incorrect parameters passed to the checker"); 327 | } 328 | } 329 | 330 | @Test 331 | public void negative() throws IOException { 332 | 333 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 334 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 335 | b.putFlag("Piranha:IsTreated", "true"); 336 | b.putFlag("Piranha:Config", "config/piranha.properties"); 337 | 338 | try { 339 | BugCheckerRefactoringTestHelper bcr = 340 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 341 | 342 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 343 | 344 | BugCheckerRefactoringTestHelper.ExpectOutput eo = 345 | bcr.addInput("XPFlagCleanerNegativeCases.java"); 346 | 347 | eo.expectUnchanged(); 348 | bcr.doTest(); 349 | } catch (ParseException pe) { 350 | pe.printStackTrace(); 351 | assert_().fail("Incorrect parameters passed to the checker"); 352 | } 353 | } 354 | 355 | @Test 356 | public void positiveCaseWithFlagNameAsVariable() throws IOException { 357 | 358 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 359 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 360 | b.putFlag("Piranha:IsTreated", "true"); 361 | b.putFlag("Piranha:Config", "config/piranha.properties"); 362 | 363 | try { 364 | BugCheckerRefactoringTestHelper bcr = 365 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 366 | 367 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 368 | 369 | bcr = addHelperClasses(bcr); 370 | bcr.addInputLines( 371 | "XPFlagCleanerSinglePositiveCase.java", 372 | "package com.uber.piranha;", 373 | "class XPFlagCleanerSinglePositiveCase {", 374 | "private static final String STALE_FLAG_CONSTANTS = \"STALE_FLAG\";", 375 | " private XPTest experimentation;", 376 | " public String evaluate() {", 377 | " // BUG: Diagnostic contains: Cleans stale XP flags", 378 | " if (experimentation.isToggleDisabled(STALE_FLAG_CONSTANTS)) { return \"X\"; }", 379 | " else { return \"Y\";}", 380 | " }", 381 | "}") 382 | .addOutputLines( 383 | "XPFlagCleanerSinglePositiveCase.java", 384 | "package com.uber.piranha;", 385 | "class XPFlagCleanerSinglePositiveCase {", 386 | " private XPTest experimentation;", 387 | " public String evaluate() {", 388 | " return \"Y\";", 389 | " }", 390 | "}"); 391 | 392 | bcr.doTest(); 393 | } catch (ParseException pe) { 394 | pe.printStackTrace(); 395 | assert_().fail("Incorrect parameters passed to the checker"); 396 | } 397 | } 398 | 399 | @Test 400 | public void positiveCaseWithFlagNameAsStringLiteral() throws IOException { 401 | 402 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 403 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 404 | b.putFlag("Piranha:IsTreated", "true"); 405 | b.putFlag("Piranha:Config", "config/piranha.properties"); 406 | 407 | try { 408 | BugCheckerRefactoringTestHelper bcr = 409 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 410 | 411 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 412 | 413 | bcr = addHelperClasses(bcr); 414 | bcr.addInputLines( 415 | "XPFlagCleanerSinglePositiveCase.java", 416 | "package com.uber.piranha;", 417 | "class XPFlagCleanerSinglePositiveCase {", 418 | " private XPTest experimentation;", 419 | " public String evaluate() {", 420 | " // BUG: Diagnostic contains: Cleans stale XP flags", 421 | " if (experimentation.isToggleDisabled(\"STALE_FLAG\")) { return \"X\"; }", 422 | " else { return \"Y\";}", 423 | " }", 424 | "}") 425 | .addOutputLines( 426 | "XPFlagCleanerSinglePositiveCase.java", 427 | "package com.uber.piranha;", 428 | "class XPFlagCleanerSinglePositiveCase {", 429 | " private XPTest experimentation;", 430 | " public String evaluate() {", 431 | " return \"Y\";", 432 | " }", 433 | "}"); 434 | 435 | bcr.doTest(); 436 | } catch (ParseException pe) { 437 | pe.printStackTrace(); 438 | assert_().fail("Incorrect parameters passed to the checker"); 439 | } 440 | } 441 | 442 | @Test 443 | public void negativeCaseWithFlagNameAsStringLiteral() throws IOException { 444 | 445 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 446 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 447 | b.putFlag("Piranha:IsTreated", "true"); 448 | b.putFlag("Piranha:Config", "config/piranha.properties"); 449 | 450 | try { 451 | BugCheckerRefactoringTestHelper bcr = 452 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 453 | 454 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 455 | 456 | bcr = addHelperClasses(bcr); 457 | bcr.addInputLines( 458 | "XPFlagCleanerSinglePositiveCase.java", 459 | "package com.uber.piranha;", 460 | "class XPFlagCleanerSinglePositiveCase {", 461 | " private XPTest experimentation;", 462 | " public String evaluate() {", 463 | " if (experimentation.isToggleDisabled(\"NOT_STALE_FLAG\")) { return \"X\"; }", 464 | " else { return \"Y\";}", 465 | " }", 466 | "}") 467 | .addOutputLines( 468 | "XPFlagCleanerSinglePositiveCase.java", 469 | "package com.uber.piranha;", 470 | "class XPFlagCleanerSinglePositiveCase {", 471 | " private XPTest experimentation;", 472 | " public String evaluate() {", 473 | " if (experimentation.isToggleDisabled(\"NOT_STALE_FLAG\")) { return \"X\"; }", 474 | " else { return \"Y\";}", 475 | " }", 476 | "}"); 477 | 478 | bcr.doTest(); 479 | } catch (ParseException pe) { 480 | pe.printStackTrace(); 481 | assert_().fail("Incorrect parameters passed to the checker"); 482 | } 483 | } 484 | 485 | @Test 486 | public void negativeCaseWithFlagNameAsVariable() throws IOException { 487 | 488 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 489 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 490 | b.putFlag("Piranha:IsTreated", "true"); 491 | b.putFlag("Piranha:Config", "config/piranha.properties"); 492 | 493 | try { 494 | BugCheckerRefactoringTestHelper bcr = 495 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 496 | 497 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 498 | 499 | bcr = addHelperClasses(bcr); 500 | bcr.addInputLines( 501 | "XPFlagCleanerSinglePositiveCase.java", 502 | "package com.uber.piranha;", 503 | "class XPFlagCleanerSinglePositiveCase {", 504 | "private static final String STALE_FLAG_CONSTANTS = \"NOT_STALE_FLAG\";", 505 | " private XPTest experimentation;", 506 | " public String evaluate() {", 507 | " if (experimentation.isToggleDisabled(STALE_FLAG_CONSTANTS)) { return \"X\"; }", 508 | " else { return \"Y\";}", 509 | " }", 510 | "}") 511 | .addOutputLines( 512 | "XPFlagCleanerSinglePositiveCase.java", 513 | "package com.uber.piranha;", 514 | "class XPFlagCleanerSinglePositiveCase {", 515 | "private static final String STALE_FLAG_CONSTANTS = \"NOT_STALE_FLAG\";", 516 | " private XPTest experimentation;", 517 | " public String evaluate() {", 518 | " if (experimentation.isToggleDisabled(STALE_FLAG_CONSTANTS)) { return \"X\"; }", 519 | " else { return \"Y\";}", 520 | " }", 521 | "}"); 522 | 523 | bcr.doTest(); 524 | } catch (ParseException pe) { 525 | pe.printStackTrace(); 526 | assert_().fail("Incorrect parameters passed to the checker"); 527 | } 528 | } 529 | 530 | @Test 531 | public void runPiranhaWithFewProperties() throws IOException { 532 | 533 | ErrorProneFlags.Builder b = ErrorProneFlags.builder(); 534 | b.putFlag("Piranha:FlagName", "STALE_FLAG"); 535 | b.putFlag("Piranha:IsTreated", "true"); 536 | b.putFlag("Piranha:Config", "config/fewer-piranha.properties"); 537 | 538 | try { 539 | BugCheckerRefactoringTestHelper bcr = 540 | BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); 541 | 542 | bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); 543 | 544 | BugCheckerRefactoringTestHelper.ExpectOutput eo = 545 | bcr.addInput("XPFlagCleanerPositiveCases.java"); 546 | eo.addOutput("XPFlagCleanerPositiveCasesTreatment.java"); 547 | 548 | bcr.doTest(); 549 | } catch (ParseException pe) { 550 | pe.printStackTrace(); 551 | assert_().fail("Incorrect parameters passed to the checker"); 552 | } 553 | } 554 | } 555 | --------------------------------------------------------------------------------