View Javadoc
1   /*
2    *    Copyright 2010-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.migration;
17  
18  import static org.junit.jupiter.api.Assertions.assertEquals;
19  import static org.junit.jupiter.api.Assertions.assertFalse;
20  import static org.junit.jupiter.api.Assertions.assertNotEquals;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.PrintWriter;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.nio.file.Paths;
30  import java.nio.file.StandardCopyOption;
31  import java.sql.Connection;
32  import java.sql.ResultSet;
33  import java.sql.Statement;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.List;
37  import java.util.Properties;
38  import java.util.Scanner;
39  import java.util.TreeSet;
40  
41  import org.apache.ibatis.migration.io.Resources;
42  import org.apache.ibatis.migration.utils.TestUtil;
43  import org.junit.jupiter.api.BeforeAll;
44  import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
45  import org.junit.jupiter.api.Order;
46  import org.junit.jupiter.api.Test;
47  import org.junit.jupiter.api.TestMethodOrder;
48  
49  import uk.org.webcompere.systemstubs.SystemStubs;
50  
51  @TestMethodOrder(OrderAnnotation.class)
52  class MigratorTest {
53  
54    private static File dir;
55  
56    private static Properties env;
57  
58    @BeforeAll
59    static void setup() throws IOException {
60      dir = Resources.getResourceAsFile("org/apache/ibatis/migration/example");
61      env = Resources.getResourceAsProperties("org/apache/ibatis/migration/example/environments/development.properties");
62    }
63  
64    @Test
65    @Order(1)
66    void testBootstrapCommand() throws Exception {
67      String output = SystemStubs.tapSystemOut(() -> {
68        Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "bootstrap", "--env=development"));
69      });
70      assertFalse(output.contains("FAILURE"));
71      assertTrue(output.contains("-- // Bootstrap.sql"));
72    }
73  
74    @Test
75    @Order(2)
76    void testStatusContainsNoPendingEntriesUsingStatusShorthand() throws Exception {
77      String output = SystemStubs.tapSystemOut(() -> {
78        Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "sta"));
79      });
80      assertFalse(output.contains("FAILURE"));
81      assertTrue(output.contains("...pending..."));
82    }
83  
84    // TODO This causes hsqldb 2.7.1 or 2.7.2 to blow up
85    @Test
86    @Order(3)
87    void testUpCommandWithSpecifiedSteps() throws Exception {
88      String output = SystemStubs.tapSystemOut(() -> {
89        Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "up", "3000"));
90      });
91      assertFalse(output.contains("FAILURE"));
92    }
93  
94    @Test
95    @Order(4)
96    void assertAuthorEmailContainsPlaceholder() throws Exception {
97      try (Connection conn = TestUtil.getConnection(env); Statement stmt = conn.createStatement();
98          ResultSet rs = stmt.executeQuery("select EMAIL from author where id = 1")) {
99        assertTrue(rs.next());
100       assertEquals("jim@${url}", rs.getString("EMAIL"));
101     }
102   }
103 
104   @Test
105   @Order(5)
106   void testDownCommandGiven2Steps() throws Exception {
107     testStatusContainsNoPendingMigrations();
108     String output = SystemStubs.tapSystemOut(() -> {
109       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "down", "2"));
110     });
111     assertFalse(output.contains("FAILURE"));
112     testStatusContainsPendingMigrations();
113   }
114 
115   @Test
116   @Order(6)
117   void testRedoCommand() throws Exception {
118     String output = SystemStubs.tapSystemOut(() -> {
119       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "status"));
120     });
121     assertFalse(output.contains("20080827200214    ...pending..."));
122     assertTrue(output.contains("20080827200216    ...pending..."));
123 
124     output = SystemStubs.tapSystemOut(() -> {
125       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "redo"));
126     });
127     assertFalse(output.contains("FAILURE"));
128     assertEquals(-1, output.indexOf("DROP TABLE post_tag"), "Should down be just one step");
129     int dropIdx = output.indexOf("DROP TABLE comment");
130     int createIdx = output.indexOf("CREATE TABLE comment (");
131     assertNotEquals(-1, dropIdx);
132     assertNotEquals(-1, createIdx);
133     assertTrue(dropIdx < createIdx);
134 
135     output = SystemStubs.tapSystemOut(() -> {
136       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "status"));
137     });
138     assertFalse(output.contains("20080827200214    ...pending..."));
139     assertTrue(output.contains("20080827200216    ...pending..."));
140 
141     output = SystemStubs.tapSystemOut(() -> {
142       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "redo", "2"));
143     });
144     assertFalse(output.contains("FAILURE"));
145     assertEquals(-1, output.indexOf("DROP TABLE blog"), "Should down be two steps");
146     List<Integer> lineNums = new ArrayList<>();
147     lineNums.add(output.indexOf("DROP TABLE comment"));
148     lineNums.add(output.indexOf("DROP TABLE post"));
149     lineNums.add(output.indexOf("CREATE TABLE post ("));
150     lineNums.add(output.indexOf("CREATE TABLE comment ("));
151     assertEquals(new TreeSet<>(lineNums).toString(), lineNums.toString());
152 
153     output = SystemStubs.tapSystemOut(() -> {
154       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "status"));
155     });
156     assertFalse(output.contains("20080827200214    ...pending..."));
157     assertTrue(output.contains("20080827200216    ...pending..."));
158   }
159 
160   @Test
161   @Order(7)
162   void testDoPendingScriptCommand() throws Exception {
163     String output = SystemStubs.tapSystemOut(() -> {
164       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "script", "pending"));
165     });
166     assertTrue(output.contains("INSERT"));
167     assertTrue(output.contains("CHANGELOG"));
168     assertFalse(output.contains("-- @UNDO"));
169 
170     output = SystemStubs.tapSystemOut(() -> {
171       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "script", "pending_undo"));
172     });
173     assertTrue(output.contains("DELETE"));
174     assertTrue(output.contains("CHANGELOG"));
175     assertTrue(output.contains("-- @UNDO"));
176   }
177 
178   @Test
179   @Order(8)
180   void testVersionCommand() throws Exception {
181     String output = SystemStubs.tapSystemOut(() -> {
182       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "version", "20080827200217"));
183     });
184     assertFalse(output.contains("FAILURE"));
185   }
186 
187   @Test
188   @Order(9)
189   void testSkippedScript() throws Exception {
190     testStatusContainsNoPendingMigrations();
191     File skipped = new File(dir + File.separator + "scripts", "20080827200215_skipped_migration.sql");
192     assertTrue(skipped.createNewFile());
193     try {
194       String output = SystemStubs.tapSystemOut(() -> {
195         Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "up"));
196       });
197       assertFalse(output.contains("FAILURE"));
198       assertEquals(1, TestUtil.countStr(output, "WARNING"));
199       assertTrue(output.contains(
200           "WARNING: Migration script '20080827200215_skipped_migration.sql' was not applied to the database."));
201     } finally {
202       skipped.delete();
203     }
204   }
205 
206   @Test
207   @Order(10)
208   void testMissingScript() throws Exception {
209     Path original = Paths.get(dir + File.separator + "scripts", "20080827200216_create_procs.sql");
210     Path renamed = Paths.get(dir + File.separator + "scripts", "20080827200216_create_procs._sql");
211     assertEquals(renamed, Files.move(original, renamed, StandardCopyOption.REPLACE_EXISTING));
212     try {
213       String output = SystemStubs.tapSystemOut(() -> {
214         Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "up"));
215       });
216       assertFalse(output.contains("FAILURE"), "Output contains: \n" + output);
217       assertTrue(output.contains("WARNING: Missing migration script. id='20080827200216', description='create procs'."),
218           "Output contains: \n" + output);
219     } finally {
220       assertEquals(original, Files.move(renamed, original, StandardCopyOption.REPLACE_EXISTING));
221     }
222   }
223 
224   @Test
225   @Order(11)
226   void testDownCommand() throws Exception {
227     String output = SystemStubs.tapSystemOut(() -> {
228       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "down"));
229     });
230     assertFalse(output.contains("FAILURE"));
231     testStatusContainsPendingMigrations();
232   }
233 
234   @Test
235   @Order(12)
236   void testPendingCommand() throws Exception {
237     String output = SystemStubs.tapSystemOut(() -> {
238       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "pending"));
239     });
240     assertFalse(output.contains("FAILURE"));
241     testStatusContainsNoPendingMigrations();
242   }
243 
244   @Test
245   @Order(13)
246   void testHelpCommand() throws Exception {
247     String output = SystemStubs.tapSystemOut(() -> {
248       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "--help"));
249     });
250     assertFalse(output.contains("FAILURE"));
251     assertTrue(output.contains("--help"));
252   }
253 
254   @Test
255   @Order(14)
256   void testDoScriptCommand() throws Exception {
257     String output = SystemStubs.tapSystemOut(() -> {
258       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "script", "20080827200212", "20080827200214"));
259     });
260     assertFalse(output.contains("FAILURE"));
261     assertFalse(output.contains("20080827200210"));
262     assertFalse(output.contains("20080827200211"));
263     assertFalse(output.contains("20080827200212"));
264     assertTrue(output.contains("20080827200213"));
265     assertTrue(output.contains("20080827200214"));
266     assertFalse(output.contains("20080827200216"));
267     assertFalse(output.contains("-- @UNDO"));
268 
269     output = SystemStubs.tapSystemOut(() -> {
270       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "script", "0", "20080827200211"));
271     });
272     assertFalse(output.contains("FAILURE"));
273     assertTrue(output.contains("20080827200210"));
274     assertTrue(output.contains("20080827200211"));
275     assertFalse(output.contains("20080827200212"));
276     assertFalse(output.contains("20080827200213"));
277     assertFalse(output.contains("20080827200214"));
278     assertFalse(output.contains("20080827200216"));
279     assertFalse(output.contains("-- @UNDO"));
280   }
281 
282   @Test
283   @Order(15)
284   void testUndoScriptCommand() throws Exception {
285     String output = SystemStubs.tapSystemOut(() -> {
286       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "script", "20080827200216", "20080827200213"));
287     });
288     assertFalse(output.contains("FAILURE"));
289     assertFalse(output.contains("20080827200210"));
290     assertFalse(output.contains("20080827200211"));
291     assertFalse(output.contains("20080827200212"));
292     assertFalse(output.contains("20080827200213"));
293     assertTrue(output.contains("20080827200214"));
294     assertTrue(output.contains("20080827200216"));
295     assertTrue(output.contains("-- @UNDO"));
296 
297     output = SystemStubs.tapSystemOut(() -> {
298       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "script", "20080827200211", "0"));
299     });
300     assertFalse(output.contains("FAILURE"));
301     assertTrue(output.contains("20080827200210"));
302     assertTrue(output.contains("20080827200211"));
303     assertFalse(output.contains("20080827200212"));
304     assertFalse(output.contains("20080827200213"));
305     assertFalse(output.contains("20080827200214"));
306     assertFalse(output.contains("20080827200216"));
307     assertFalse(output.contains("DELETE FROM CHANGELOG WHERE ID = 20080827200210;"));
308     assertTrue(output.contains("-- @UNDO"));
309   }
310 
311   @Test
312   void shouldScriptCommandFailIfSameVersion() throws Exception {
313     String output = SystemStubs.tapSystemOut(() -> {
314       int exitCode = SystemStubs.catchSystemExit(() -> {
315         Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "script", "20080827200211", "20080827200211"));
316       });
317       assertEquals(1, exitCode);
318     });
319     assertTrue(output.contains("FAILURE"));
320   }
321 
322   @Test
323   void shouldInitTempDirectory() throws Exception {
324     File basePath = TestUtil.getTempDir();
325     Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "init"));
326     assertNotNull(basePath.list());
327     assertEquals(4, basePath.list().length);
328     File scriptPath = new File(basePath.getCanonicalPath() + File.separator + "scripts");
329     assertEquals(3, scriptPath.list().length);
330     Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "new", "test new migration"));
331     assertEquals(4, scriptPath.list().length);
332     assertTrue(TestUtil.deleteDirectory(basePath), "delete temp dir");
333   }
334 
335   @Test
336   void shouldRespectIdPattern() throws Exception {
337     String idPattern = "000";
338     File basePath = TestUtil.getTempDir();
339     Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "--idpattern=" + idPattern, "init"));
340     File changelog = new File(
341         basePath.getCanonicalPath() + File.separator + "scripts" + File.separator + "001_create_changelog.sql");
342     assertTrue(changelog.exists());
343     Migrator.main(
344         TestUtil.args("--path=" + basePath.getAbsolutePath(), "--idpattern=" + idPattern, "new", "new migration"));
345     File newMigration = new File(
346         basePath.getCanonicalPath() + File.separator + "scripts" + File.separator + "003_new_migration.sql");
347     assertTrue(newMigration.exists());
348     assertTrue(TestUtil.deleteDirectory(basePath), "delete temp dir");
349   }
350 
351   @Test
352   void useCustomTemplate() throws Exception {
353     String desc = "test new migration";
354     File basePath = TestUtil.getTempDir();
355     Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "init"));
356     assertNotNull(basePath.list());
357     assertEquals(4, basePath.list().length);
358     File scriptPath = new File(basePath.getCanonicalPath() + File.separator + "scripts");
359     assertEquals(3, scriptPath.list().length);
360 
361     File templatePath = File.createTempFile("customTemplate", "sql");
362     try (PrintWriter writer = new PrintWriter(templatePath)) {
363       writer.println("// ${description}");
364     }
365     Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "new", desc,
366         "--template=" + templatePath.getAbsolutePath()));
367     String[] scripts = scriptPath.list();
368     Arrays.sort(scripts);
369     assertEquals(4, scripts.length);
370     try (Scanner scanner = new Scanner(new File(scriptPath, scripts[scripts.length - 2]))) {
371       if (scanner.hasNextLine()) {
372         assertEquals("// " + desc, scanner.nextLine());
373       }
374     }
375     templatePath.delete();
376     assertTrue(TestUtil.deleteDirectory(basePath), "delete temp dir");
377   }
378 
379   @Test
380   void useCustomTemplateWithNoValue() throws Exception {
381     File basePath = TestUtil.getTempDir();
382     Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "init"));
383     assertNotNull(basePath.list());
384     assertEquals(4, basePath.list().length);
385     File scriptPath = new File(basePath.getCanonicalPath() + File.separator + "scripts");
386     assertEquals(3, scriptPath.list().length);
387 
388     File templatePath = File.createTempFile("customTemplate", "sql");
389     templatePath.createNewFile();
390     Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "new", "test new migration", "--template="));
391     assertEquals(4, scriptPath.list().length);
392     templatePath.delete();
393     assertTrue(TestUtil.deleteDirectory(basePath), "delete temp dir");
394   }
395 
396   @Test
397   void useCustomTemplateWithBadPath() throws Exception {
398     System.setProperty("migrationsHome", TestUtil.getTempDir().getAbsolutePath());
399     File basePath = TestUtil.getTempDir();
400     Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "init"));
401     assertNotNull(basePath.list());
402     assertEquals(4, basePath.list().length);
403     File scriptPath = new File(basePath.getCanonicalPath() + File.separator + "scripts");
404     assertEquals(3, scriptPath.list().length);
405 
406     String output = SystemStubs.tapSystemOut(() -> {
407       Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "new", "test new migration"));
408     });
409     assertEquals(4, scriptPath.list().length);
410     assertTrue(output
411         .contains("Your migrations configuration did not find your custom template.  Using the default template."));
412     assertTrue(TestUtil.deleteDirectory(basePath), "delete temp dir");
413   }
414 
415   @Test
416   void shouldSuppressOutputIfQuietOptionEnabled() throws Throwable {
417     System.setProperty("migrationsHome", TestUtil.getTempDir().getAbsolutePath());
418     File basePath = TestUtil.getTempDir();
419     String output = SystemStubs.tapSystemOut(() -> {
420       Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "--quiet", "init"));
421     });
422     assertFalse(output.contains("Initializing:"));
423     assertNotNull(basePath.list());
424     assertTrue(TestUtil.deleteDirectory(basePath), "delete temp dir");
425   }
426 
427   @Test
428   void shouldColorizeSuccessOutputIfColorOptionEnabled() throws Throwable {
429     System.setProperty("migrationsHome", TestUtil.getTempDir().getAbsolutePath());
430     File basePath = TestUtil.getTempDir();
431     String output = SystemStubs.tapSystemOut(() -> {
432       Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "--color", "init"));
433     });
434     assertTrue(output.contains(ConsoleColors.GREEN + "SUCCESS"));
435     assertNotNull(basePath.list());
436     assertTrue(TestUtil.deleteDirectory(basePath), "delete temp dir");
437   }
438 
439   @Test
440   void shouldColorizeFailureOutputIfColorOptionEnabled() throws Throwable {
441     System.setProperty("migrationsHome", TestUtil.getTempDir().getAbsolutePath());
442     File basePath = TestUtil.getTempDir();
443     String output = SystemStubs.tapSystemOut(() -> {
444       int exitCode = SystemStubs.catchSystemExit(() -> {
445         Migrator.main(TestUtil.args("--path=" + basePath.getAbsolutePath(), "--color", "new"));
446       });
447       assertEquals(1, exitCode);
448     });
449     assertTrue(output.contains(ConsoleColors.RED + "FAILURE"));
450     assertTrue(TestUtil.deleteDirectory(basePath), "delete temp dir");
451   }
452 
453   @Test
454   void shouldShowErrorOnMissingChangelog() throws Throwable {
455     // gh-220
456     try {
457       System.setProperty("migrations_changelog", "changelog1");
458       String output = SystemStubs.tapSystemOut(() -> {
459         Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "up", "1"));
460       });
461       assertFalse(output.contains("FAILURE"));
462 
463       System.setProperty("migrations_changelog", "changelog2");
464       output = SystemStubs.tapSystemOut(() -> {
465         int exitCode = SystemStubs.catchSystemExit(() -> {
466           Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "pending"));
467         });
468         assertEquals(1, exitCode);
469       });
470       assertTrue(output.contains("FAILURE"));
471       assertTrue(output.contains("Change log doesn't exist, no migrations applied.  Try running 'up' instead."));
472     } finally {
473       System.clearProperty("migrations_changelog");
474     }
475   }
476 
477   @Test
478   void testInfoCommand() throws Exception {
479     String output = SystemStubs.tapSystemOut(() -> {
480       Migrator.main(TestUtil.args("info"));
481     });
482     assertFalse(output.contains("null"), output);
483   }
484 
485   @Test
486   void testInfoWithNonExistentBasePath() throws Exception {
487     File baseDir = TestUtil.getTempDir();
488     assertTrue(baseDir.delete()); // remove empty dir
489     assertFalse(baseDir.exists(), "directory does not exist");
490     String output = SystemStubs.tapSystemOut(() -> {
491       Migrator.main(TestUtil.args("info", "--path=" + baseDir.getAbsolutePath()));
492     });
493     assertFalse(output.contains("Migrations path must be a directory"), "base path not required for info");
494     assertFalse(output.contains("null"), output);
495   }
496 
497   @Test
498   void testInitWithNonExistentBasePath() throws Exception {
499     File baseDir = TestUtil.getTempDir();
500     assertTrue(baseDir.delete()); // remove empty dir
501     assertFalse(baseDir.exists(), "directory does not exist");
502     String output = SystemStubs
503         .tapSystemOut(() -> Migrator.main(TestUtil.args("init", "--path=" + baseDir.getAbsolutePath())));
504     assertFalse(output.contains("Migrations path must be a directory"), output);
505     assertTrue(new File(baseDir, "README").exists(), "README created");
506     assertTrue(new File(baseDir, "environments").isDirectory(), "environments directory created");
507     assertTrue(TestUtil.deleteDirectory(baseDir), "delete temp dir");
508   }
509 
510   private void testStatusContainsPendingMigrations() throws Exception {
511     String output = SystemStubs.tapSystemOut(() -> {
512       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "status"));
513     });
514     assertFalse(output.contains("FAILURE"));
515     assertTrue(output.contains("...pending..."));
516   }
517 
518   private void testStatusContainsNoPendingMigrations() throws Exception {
519     String output = SystemStubs.tapSystemOut(() -> {
520       Migrator.main(TestUtil.args("--path=" + dir.getAbsolutePath(), "status"));
521     });
522     assertFalse(output.contains("FAILURE"));
523     assertFalse(output.contains("...pending..."));
524   }
525 
526 }