Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 109 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
PopulateWithTestData | |
0.00% |
0 / 104 |
|
0.00% |
0 / 6 |
650 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
setupServices | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 49 |
|
0.00% |
0 / 1 |
56 | |||
cleanupTestData | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
6 | |||
getRandomValueFromDistribution | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
assertOptions | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
72 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\ReadingLists\Maintenance; |
4 | |
5 | use MediaWiki\Extension\ReadingLists\ReadingListRepository; |
6 | use MediaWiki\Extension\ReadingLists\ReadingListRepositoryException; |
7 | use MediaWiki\Extension\ReadingLists\Utils; |
8 | use MediaWiki\Maintenance\Maintenance; |
9 | use Wikimedia\Rdbms\IDatabase; |
10 | use Wikimedia\Rdbms\LBFactory; |
11 | |
12 | require_once getenv( 'MW_INSTALL_PATH' ) !== false |
13 | ? getenv( 'MW_INSTALL_PATH' ) . '/maintenance/Maintenance.php' |
14 | : __DIR__ . '/../../../maintenance/Maintenance.php'; |
15 | |
16 | /** |
17 | * Fill the database with test data, or remove it. |
18 | */ |
19 | class PopulateWithTestData extends Maintenance { |
20 | |
21 | /** @var LBFactory */ |
22 | private $loadBalancerFactory; |
23 | |
24 | /** @var IDatabase */ |
25 | private $dbw; |
26 | |
27 | public function __construct() { |
28 | parent::__construct(); |
29 | $this->addDescription( 'Fill the database with test data, or remove it.' ); |
30 | $this->addOption( 'users', 'Number of users', false, true ); |
31 | $this->addOption( 'lists', 'Lists per user (number or stats distribution)', false, true ); |
32 | $this->addOption( 'entries', 'Entries per list (number or stats distribution)', false, true ); |
33 | $this->addOption( 'cleanup', 'Delete lists which look like test data' ); |
34 | $this->requireExtension( 'ReadingLists' ); |
35 | if ( !extension_loaded( 'stats' ) ) { |
36 | $this->fatalError( 'Requires the stats PHP extension' ); |
37 | } |
38 | } |
39 | |
40 | private function setupServices() { |
41 | // Can't do this in the constructor, initialization not done yet. |
42 | $services = $this->getServiceContainer(); |
43 | $this->loadBalancerFactory = $services->getDBLoadBalancerFactory(); |
44 | $this->dbw = $this->loadBalancerFactory->getPrimaryDatabase( Utils::VIRTUAL_DOMAIN ); |
45 | } |
46 | |
47 | /** |
48 | * @inheritDoc |
49 | */ |
50 | public function execute() { |
51 | $this->setupServices(); |
52 | $this->assertOptions(); |
53 | if ( $this->getOption( 'cleanup' ) ) { |
54 | $this->cleanupTestData(); |
55 | return; |
56 | } |
57 | |
58 | $projects = $this->dbw->newSelectQueryBuilder() |
59 | ->select( 'rlp_id' ) |
60 | ->from( 'reading_list_project' ) |
61 | ->caller( __METHOD__ )->fetchFieldValues(); |
62 | if ( !$projects ) { |
63 | $this->fatalError( 'No projects! Please set up some' ); |
64 | } |
65 | $totalLists = $totalEntries = 0; |
66 | stats_rand_setall( mt_rand(), mt_rand() ); |
67 | $users = $this->getOption( 'users' ); |
68 | for ( $i = 0; $i < $users; $i++ ) { |
69 | // The test data is for performance testing so we don't care whether the user exists. |
70 | $centralId = 1000 + $i; |
71 | $repository = new ReadingListRepository( $centralId, $this->loadBalancerFactory ); |
72 | try { |
73 | $repository->setupForUser(); |
74 | $i++; |
75 | // HACK mark default list so it will be deleted together with the rest |
76 | $this->dbw->newUpdateQueryBuilder() |
77 | ->update( 'reading_list' ) |
78 | ->set( [ 'rl_description' => __FILE__ ] ) |
79 | ->where( [ 'rl_user_id' => $centralId, 'rl_is_default' => 1 ] ) |
80 | ->caller( __METHOD__ )->execute(); |
81 | } catch ( ReadingListRepositoryException $e ) { |
82 | // Instead of trying to find a user ID that's not used yet, we'll be lazy |
83 | // and just ignore "already set up" errors. |
84 | } |
85 | $lists = $this->getRandomValueFromDistribution( $this->getOption( 'lists' ) ); |
86 | for ( $j = 0; $j < $lists; $j++, $totalLists++ ) { |
87 | $list = $repository->addList( "test_$j", __FILE__ ); |
88 | $entries = $this->getRandomValueFromDistribution( $this->getOption( 'entries' ) ); |
89 | $rows = []; |
90 | for ( $k = 0; $k < $entries; $k++, $totalEntries++ ) { |
91 | $project = $projects[array_rand( $projects )]; |
92 | // Calling addListEntry for each row separately would be a bit slow. |
93 | $rows[] = [ |
94 | 'rle_rl_id' => $list->rl_id, |
95 | 'rle_user_id' => $centralId, |
96 | 'rle_rlp_id' => $project, |
97 | 'rle_title' => "Test_$k", |
98 | ]; |
99 | } |
100 | $this->dbw->newInsertQueryBuilder() |
101 | ->insertInto( 'reading_list_entry' ) |
102 | ->rows( $rows ) |
103 | ->caller( __METHOD__ )->execute(); |
104 | $this->dbw->newUpdateQueryBuilder() |
105 | ->update( 'reading_list' ) |
106 | ->set( [ 'rl_size' => $entries ] ) |
107 | ->where( [ 'rl_id' => $list->rl_id ] ) |
108 | ->caller( __METHOD__ )->execute(); |
109 | } |
110 | $this->output( '.' ); |
111 | } |
112 | $this->output( "\nAdded $totalLists lists and $totalEntries entries for $users users\n" ); |
113 | } |
114 | |
115 | private function cleanupTestData() { |
116 | $services = $this->getServiceContainer(); |
117 | $ids = $this->dbw->newSelectQueryBuilder() |
118 | ->select( 'rl_id' ) |
119 | ->from( 'reading_list' ) |
120 | ->where( [ 'rl_description' => __FILE__ ] ) |
121 | ->caller( __METHOD__ )->fetchFieldValues(); |
122 | if ( !$ids ) { |
123 | $this->output( "Noting to clean up\n" ); |
124 | return; |
125 | } |
126 | $this->dbw->newDeleteQueryBuilder() |
127 | ->deleteFrom( 'reading_list_entry' ) |
128 | ->where( [ 'rle_rl_id' => $ids ] ) |
129 | ->caller( __METHOD__ )->execute(); |
130 | $entries = $this->dbw->affectedRows(); |
131 | $this->dbw->newDeleteQueryBuilder() |
132 | ->deleteFrom( 'reading_list' ) |
133 | ->where( [ 'rl_description' => __FILE__ ] ) |
134 | ->caller( __METHOD__ )->execute(); |
135 | $lists = $this->dbw->affectedRows(); |
136 | $this->output( "Deleted $lists lists and $entries entries\n" ); |
137 | } |
138 | |
139 | /** |
140 | * Get a random value according to some distribution. The parameter is either a constant |
141 | * (in which case it will be returned) or a distribution descriptor in the form of |
142 | * '<dist>,<param1>,<param2>,...' (no spaces) where <dist> refers to one of the stats_rand_gen_* |
143 | * methods (e.g. 'exponential,1' for an exponential distribution with λ=1, or 'normal,0,1' for |
144 | * a normal distribution with µ=0, ρ=1). |
145 | * The result is normalized to be a nonnegative integer. |
146 | * @param string $distribution |
147 | * @return int |
148 | */ |
149 | private function getRandomValueFromDistribution( $distribution ) { |
150 | $params = explode( ',', $distribution ); |
151 | $type = trim( array_shift( $params ) ); |
152 | if ( is_numeric( $type ) ) { |
153 | return (int)$type; |
154 | } |
155 | $function = "stats_rand_gen_$type"; |
156 | if ( |
157 | !preg_match( '/[a-z_]+/', $type ) |
158 | || !function_exists( $function ) |
159 | ) { |
160 | $this->error( "invalid distribution: $distribution (could not parse '$type')" ); |
161 | } |
162 | $params = array_map( function ( $param ) use ( $distribution ) { |
163 | if ( !is_numeric( $param ) ) { |
164 | $this->error( "invalid distribution: $distribution (could not parse '$param')" ); |
165 | } |
166 | return (float)$param; |
167 | }, $params ); |
168 | return max( (int)call_user_func_array( $function, $params ), 0 ); |
169 | } |
170 | |
171 | private function assertOptions() { |
172 | if ( $this->hasOption( 'cleanup' ) ) { |
173 | if ( |
174 | $this->hasOption( 'users' ) |
175 | || $this->hasOption( 'lists' ) |
176 | || $this->hasOption( 'entries' ) |
177 | ) { |
178 | $this->fatalError( "'cleanup' cannot be used together with other options" ); |
179 | } |
180 | } else { |
181 | if ( |
182 | !$this->hasOption( 'users' ) |
183 | || !$this->hasOption( 'lists' ) |
184 | || !$this->hasOption( 'entries' ) |
185 | ) { |
186 | $this->fatalError( "'users', 'lists' and 'entries' are required in non-cleanup mode" ); |
187 | } |
188 | } |
189 | } |
190 | |
191 | } |
192 | |
193 | $maintClass = PopulateWithTestData::class; |
194 | require_once RUN_MAINTENANCE_IF_MAIN; |