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 MediaWiki\Context\RequestContext;
7use Wikimedia\Assert\Assert;
8
9/**
10 * Reports UserTesting bucketing decision
11 *
12 * See UserTestingEngine for initialization.
13 *
14 * @license GPL-2.0-or-later
15 */
16class UserTestingStatus {
17    /** Bucketing decision for the main request context */
18    private static ?self $instance = null;
19
20    /** @var ?string The name of the active test, or null if none */
21    private $testName;
22
23    /** @var ?string The name of the active bucket, or null if no active test */
24    private $bucket;
25
26    /**
27     * @return bool True when a bucketing decision has been made for the main
28     *  request context
29     */
30    public static function hasInstance(): bool {
31        return self::$instance !== null;
32    }
33
34    /**
35     * Reports bucketing decision for the main request context
36     *
37     * If not created yet, uses configuration and query string from request context
38     * to make a bucketing decision and activate that decision. This must be
39     * called as early in the request as is sensible to ensure the test configuration
40     * is applied.
41     */
42    public static function getInstance(): self {
43        if ( self::$instance === null ) {
44            $context = RequestContext::getMain();
45            $trigger = $context->getRequest()->getVal( 'cirrusUserTesting' );
46            // The current method of ensuring user testing is always initialized is
47            // sloppy, if we used the context config everything that touches
48            // ElasticsearchIntermediary would fail unit testing.
49            if ( defined( 'MW_PHPUNIT_TEST' ) ) {
50                $config = new HashConfig( [
51                    CirrusConfigNames::UserTesting => [],
52                    CirrusConfigNames::ActiveTest => false,
53                ] );
54            } else {
55                $config = $context->getConfig();
56            }
57            $engine = UserTestingEngine::fromConfig( $config );
58            self::$instance = $engine->decideActiveTest( $trigger );
59            // The singleton here also doubles as a marker for if we've already
60            // applied the test configuration. This should be the only place
61            // to activate a test outside maintenance scripts.
62            $engine->activateTest( self::$instance );
63        }
64        return self::$instance;
65    }
66
67    /**
68     * @param string $testName
69     * @param string $bucket
70     * @return self status representing a test bucket active for this request
71     */
72    public static function active( string $testName, string $bucket ): self {
73        return new self( $testName, $bucket );
74    }
75
76    /**
77     * @return self status representing user testing as inactive
78     */
79    public static function inactive(): self {
80        return new self( null, null );
81    }
82
83    private function __construct( ?string $testName, ?string $bucket ) {
84        Assert::precondition( ( $testName === null ) === ( $bucket === null ),
85            'Either testName and bucket are both null or both strings' );
86        $this->testName = $testName;
87        $this->bucket = $bucket;
88    }
89
90    /**
91     * @return bool True when a test is currently active
92     */
93    public function isActive(): bool {
94        return $this->testName !== null;
95    }
96
97    /**
98     * @return string
99     * @throws NoActiveTestException
100     */
101    public function getTestName(): string {
102        if ( $this->testName === null ) {
103            throw new NoActiveTestException();
104        }
105        return $this->testName;
106    }
107
108    /**
109     * @return string
110     * @throws NoActiveTestException
111     */
112    public function getBucket(): string {
113        if ( $this->bucket === null ) {
114            throw new NoActiveTestException();
115        }
116        return $this->bucket;
117    }
118
119    /**
120     * @return string When active returns a string that will enable the same
121     *  test configuration when provided to UserTestingEngine.
122     * @throws NoActiveTestException
123     */
124    public function getTrigger(): string {
125        if ( $this->testName === null || $this->bucket == null ) {
126            throw new NoActiveTestException();
127        }
128        return "{$this->testName}:{$this->bucket}";
129    }
130}