Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
53.33% covered (warning)
53.33%
16 / 30
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserTestingStatus
53.33% covered (warning)
53.33%
16 / 30
77.78% covered (warning)
77.78%
7 / 9
37.87
0.00% covered (danger)
0.00%
0 / 1
 hasInstance
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInstance
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 active
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 inactive
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 isActive
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTestName
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getBucket
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getTrigger
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace CirrusSearch;
4
5use MediaWiki\Config\HashConfig;
6use RequestContext;
7use Wikimedia\Assert\Assert;
8
9/**
10 * Reports UserTesting bucketing decision
11 *
12 * See UserTestingEngine for initialization.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 * http://www.gnu.org/copyleft/gpl.html
28 */
29class UserTestingStatus {
30    /** @var ?UserTestingStatus Bucketing decision for the main request context */
31    private static $instance;
32
33    /** @var ?string The name of the active test, or null if none */
34    private $testName;
35
36    /** @var ?string The name of the active bucket, or null if no active test */
37    private $bucket;
38
39    /**
40     * @return bool True when a bucketing decision has been made for the main
41     *  request context
42     */
43    public static function hasInstance(): bool {
44        return self::$instance !== null;
45    }
46
47    /**
48     * Reports bucketing decision for the main request context
49     *
50     * If not created yet, uses configuration and query string from request context
51     * to make a bucketing decision and activate that decision. This must be
52     * called as early in the request as is sensible to ensure the test configuration
53     * is applied.
54     *
55     * @return UserTestingStatus
56     */
57    public static function getInstance(): UserTestingStatus {
58        if ( self::$instance === null ) {
59            $context = RequestContext::getMain();
60            $trigger = $context->getRequest()->getVal( 'cirrusUserTesting' );
61            // The current method of ensuring user testing is always initialized is
62            // sloppy, if we used the context config everything that touches
63            // ElasticsearchIntermediary would fail unit testing.
64            if ( defined( 'MW_PHPUNIT_TEST' ) ) {
65                $config = new HashConfig( [
66                    'CirrusSearchUserTesting' => [],
67                    'CirrusSearchActiveTest' => false,
68                ] );
69            } else {
70                $config = $context->getConfig();
71            }
72            $engine = UserTestingEngine::fromConfig( $config );
73            self::$instance = $engine->decideActiveTest( $trigger );
74            // The singleton here also doubles as a marker for if we've already
75            // applied the test configuration. This should be the only place
76            // to activate a test outside maintenance scripts.
77            $engine->activateTest( self::$instance );
78        }
79        return self::$instance;
80    }
81
82    /**
83     * @param string $testName
84     * @param string $bucket
85     * @return UserTestingStatus status representing a test bucket active for this request
86     */
87    public static function active( string $testName, string $bucket ): UserTestingStatus {
88        return new self( $testName, $bucket );
89    }
90
91    /**
92     * @return UserTestingStatus status representing user testing as inactive
93     */
94    public static function inactive(): UserTestingStatus {
95        return new self( null, null );
96    }
97
98    private function __construct( ?string $testName, ?string $bucket ) {
99        Assert::precondition( ( $testName === null ) === ( $bucket === null ),
100            'Either testName and bucket are both null or both strings' );
101        $this->testName = $testName;
102        $this->bucket = $bucket;
103    }
104
105    /**
106     * @return bool True when a test is currently active
107     */
108    public function isActive(): bool {
109        return $this->testName !== null;
110    }
111
112    /**
113     * @return string
114     * @throws NoActiveTestException
115     */
116    public function getTestName(): string {
117        if ( $this->testName === null ) {
118            throw new NoActiveTestException();
119        }
120        return $this->testName;
121    }
122
123    /**
124     * @return string
125     * @throws NoActiveTestException
126     */
127    public function getBucket(): string {
128        if ( $this->bucket === null ) {
129            throw new NoActiveTestException();
130        }
131        return $this->bucket;
132    }
133
134    /**
135     * @return string When active returns a string that will enable the same
136     *  test configuration when provided to UserTestingEngine.
137     */
138    public function getTrigger(): string {
139        if ( $this->testName === null || $this->bucket == null ) {
140            throw new NoActiveTestException();
141        }
142        return "{$this->testName}:{$this->bucket}";
143    }
144}