-
Notifications
You must be signed in to change notification settings - Fork 158
Expand file tree
/
Copy pathEnd2EndTest.php
More file actions
250 lines (223 loc) · 10.3 KB
/
Copy pathEnd2EndTest.php
File metadata and controls
250 lines (223 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
/**
* End-to-End Test for DBDiff
*
* This test compares the SQL generated by DBDiff against pre-recorded expected files.
* It uses two temporary databases (diff1 and diff2) to perform the comparison.
*/
class End2EndTest extends PHPUnit\Framework\TestCase
{
// Database connection configuration
private $host;
private $port = 3306;
private $user = "root";
private $pass = "rootpass";
private $db1 = "diff1";
private $db2 = "diff2";
// Filenames for migration output and expectation baselines
private $migration_actual = 'migration_actual';
private $migration_expected = 'migration_expected';
// Database connection and metadata
private $db;
private $databaseMajor;
/**
* Set up the test environment.
* Note: We use setUp() instead of __construct() for PHPUnit 11 compatibility.
*/
protected function setUp(): void
{
// 1. Identify Database Host
// We use DB_HOST environment variable (provided by Docker) or fallback to 'db'
$this->host = getenv('DB_HOST') ?: ($_ENV['DB_HOST'] ?? ($_SERVER['DB_HOST'] ?? 'db'));
$debug = getenv('DBDIFF_DEBUG') === 'true';
if ($debug) {
echo "\nDEBUG: DB_HOST environment variable: " . (getenv('DB_HOST') ?: 'NOT_SET');
echo "\nDEBUG: Using database host: " . $this->host;
echo "\nDEBUG: Full connection string will be: mysql:host=" . $this->host . ";port=" . $this->port . "\n";
}
// 2. Establish Database Connection with Retry Logic
// This ensures tests don't fail if the MySQL container is still warming up
$maxRetries = 3;
$retryDelay = 2;
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
try {
$this->db = new PDO("mysql:host=$this->host;port=$this->port", $this->user, $this->pass);
if ($debug) {
echo "\nSuccessfully connected to database on attempt $attempt\n";
}
break;
} catch (PDOException $e) {
if ($debug) {
echo "\nConnection attempt $attempt failed: " . $e->getMessage();
}
if ($attempt === $maxRetries) {
if ($debug) {
echo "\nFailed to connect after $maxRetries attempts. Please check if MySQL is running.\n";
}
$this->fail("Failed to connect to database after $maxRetries attempts: " . $e->getMessage());
}
if ($debug) {
echo "\nRetrying in $retryDelay seconds...\n";
}
sleep($retryDelay);
}
}
// 3. Detect Database Version
// SQL generated by MySQL can vary slightly by version. We append the major version
// to the expectation filename (e.g., migration_expected_8) for byte-for-byte matching.
// When DBDIFF_ENGINE is set (e.g., 'dolt'), use that as the suffix instead so
// MySQL-compatible variants can have their own expected output files.
$databaseVersion = explode(".", $this->db->getAttribute(PDO::ATTR_SERVER_VERSION));
$this->databaseMajor = $databaseVersion[0];
$engine = getenv('DBDIFF_ENGINE') ?: null;
if ($engine) {
$this->migration_expected = $this->migration_expected . "_" . $engine;
$this->migration_actual = $this->migration_actual . "_" . $engine;
} else {
$this->migration_expected = $this->migration_expected . "_" . $this->databaseMajor;
$this->migration_actual = $this->migration_actual . "_" . $this->databaseMajor;
}
if ($debug) {
echo "\nDatabase server major version is: " . $this->databaseMajor . "\n";
}
// 4. Reset Test Databases
// Ensure we start with a clean slate for every test execution
$this->db->exec("DROP DATABASE IF EXISTS `$this->db1`;");
$this->db->exec("DROP DATABASE IF EXISTS `$this->db2`;");
$this->db->exec("CREATE DATABASE `$this->db1`;");
$this->db->exec("CREATE DATABASE `$this->db2`;");
// 5. Initialize Database Fixtures
// Apply the pre-defined SQL structures to our test databases
$this->db->query("use `$this->db1`");
$this->db->exec(file_get_contents('tests/end2end/db1-up.sql'));
$this->db->query("use `$this->db2`");
$this->db->exec(file_get_contents('tests/end2end/db2-up.sql'));
}
/**
* Main test runner
*/
public function testAll()
{
// Prepare CLI arguments as if they were passed via terminal
$GLOBALS['argv'] = [
"",
"--server1=$this->user:$this->pass@$this->host:$this->port",
"--template=templates/simple-db-migrate.tmpl",
"--type=all",
"--include=all",
"--nocomments",
"--output=./tests/end2end/$this->migration_actual",
"server1.$this->db1:server1.$this->db2"
];
// Run DBDiff and capture output
// We use try-finally to ensure the output buffer is ALWAYS closed,
// preventing 'Risky Test' warnings in PHPUnit.
ob_start();
try {
$dbdiff = new DBDiff\DBDiff;
$dbdiff->run();
} finally {
ob_end_clean();
}
$migration_actual_content = file_get_contents("./tests/end2end/$this->migration_actual");
$migration_expected_path = "./tests/end2end/$this->migration_expected";
// Record Mode Logic
// If DBDIFF_RECORD_MODE is true, we update the gold standard file instead of asserting.
// This is useful when the environment or deterministic sorting order changes.
if (($_ENV['DBDIFF_RECORD_MODE'] ?? 'false') === 'true') {
file_put_contents($migration_expected_path, $migration_actual_content);
echo "\n📝 Recorded NEW deterministic expected output for Legacy End2EndTest (MySQL $this->databaseMajor)\n";
$this->addToAssertionCount(1);
} else {
if (!file_exists($migration_expected_path)) {
$this->fail("Expected output file not found: $migration_expected_path. Run with DBDIFF_RECORD_MODE=true to create it.");
}
$migration_expected_content = trim(file_get_contents($migration_expected_path));
// Assert that the generated SQL exactly matches our deterministic baseline
$this->assertEquals($migration_expected_content, trim($migration_actual_content));
}
}
/**
* Clean up after tests
*/
protected function tearDown(): void
{
// Remove temporary databases
$this->db->exec("DROP DATABASE IF EXISTS `$this->db1`;");
$this->db->exec("DROP DATABASE IF EXISTS `$this->db2`;");
// Remove temporary output file
if (file_exists("./tests/end2end/$this->migration_actual")) {
unlink("./tests/end2end/$this->migration_actual");
}
}
/**
* Bug #3 regression: database names containing hyphens must be
* backtick-escaped in all generated SQL.
*
* Before the fix, `FROM my-db.table` was emitted without quoting,
* causing MySQL to interpret the hyphen as subtraction (error 1064).
* This test runs the same diff with databases named `diff-1`/`diff-2`
* and asserts that DBDiff completes without throwing and produces output.
*/
public function testHyphenatedDatabaseNames(): void
{
$hyphenDb1 = 'diff-1';
$hyphenDb2 = 'diff-2';
$outputFile = './tests/end2end/migration_hyphen_actual';
$this->db->exec("DROP DATABASE IF EXISTS `$hyphenDb1`;");
$this->db->exec("DROP DATABASE IF EXISTS `$hyphenDb2`;");
$this->db->exec("CREATE DATABASE `$hyphenDb1`;");
$this->db->exec("CREATE DATABASE `$hyphenDb2`;");
try {
$this->db->query("use `$hyphenDb1`");
$this->db->exec(file_get_contents('tests/end2end/db1-up.sql'));
$this->db->query("use `$hyphenDb2`");
$this->db->exec(file_get_contents('tests/end2end/db2-up.sql'));
$GLOBALS['argv'] = [
'',
"--server1=$this->user:$this->pass@$this->host:$this->port",
'--template=templates/simple-db-migrate.tmpl',
'--type=all',
'--include=all',
'--nocomments',
"--output=$outputFile",
"server1.{$hyphenDb1}:server1.{$hyphenDb2}",
];
ob_start();
try {
$dbdiff = new DBDiff\DBDiff;
$dbdiff->run();
} finally {
ob_end_clean();
}
// If hyphens weren't escaped, DBDiff would throw a PDO syntax error
// and no output file would be written.
$this->assertFileExists(
$outputFile,
'Bug #3 regression: DBDiff must not throw a SQL error for hyphenated DB names'
);
$actual = trim(file_get_contents($outputFile));
$this->assertNotEmpty(
$actual,
'Bug #3 regression: diff between hyphenated databases must produce output'
);
// The output must match the baseline recorded for the non-hyphenated equivalent
// (same schema/data, only the DB name differs — SQL output is identical).
$expectedPath = "./tests/end2end/{$this->migration_expected}";
if (file_exists($expectedPath)) {
$this->assertEquals(
trim(file_get_contents($expectedPath)),
$actual,
'Bug #3 regression: hyphenated DB output must match the standard baseline'
);
}
} finally {
$this->db->exec("DROP DATABASE IF EXISTS `$hyphenDb1`;");
$this->db->exec("DROP DATABASE IF EXISTS `$hyphenDb2`;");
if (file_exists($outputFile)) {
unlink($outputFile);
}
}
}
}