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