]> source.dussan.org Git - sonarqube.git/blob
e3aae2873331584f4bbb24025e80143a7f01ed44
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.server.platform.db.migration.step;
21
22 import java.sql.SQLException;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.Iterator;
27 import java.util.List;
28 import org.junit.Rule;
29 import org.junit.Test;
30 import org.slf4j.event.Level;
31 import org.sonar.api.testfixtures.log.LogTester;
32 import org.sonar.core.platform.SpringComponentContainer;
33 import org.sonar.server.platform.db.migration.engine.MigrationContainer;
34 import org.sonar.server.platform.db.migration.engine.SimpleMigrationContainer;
35 import org.sonar.server.platform.db.migration.history.MigrationHistory;
36
37 import static com.google.common.base.Preconditions.checkState;
38 import static java.util.Arrays.asList;
39 import static org.assertj.core.api.Assertions.assertThat;
40 import static org.assertj.core.api.Assertions.fail;
41 import static org.mockito.Mockito.mock;
42
43 public class MigrationStepsExecutorImplTest {
44   @Rule
45   public LogTester logTester = new LogTester();
46
47   private MigrationContainer migrationContainer = new SimpleMigrationContainer();
48   private MigrationHistory migrationHistor = mock(MigrationHistory.class);
49   private MigrationStepsExecutorImpl underTest = new MigrationStepsExecutorImpl(migrationContainer, migrationHistor);
50
51   @Test
52   public void execute_does_not_fail_when_stream_is_empty_and_log_start_stop_INFO() {
53     underTest.execute(Collections.emptyList());
54
55     assertThat(logTester.logs()).hasSize(2);
56     assertLogLevel(Level.INFO, "Executing DB migrations...", "Executed DB migrations: success | time=");
57   }
58
59   @Test
60   public void execute_fails_with_ISE_if_no_instance_of_computation_step_exist_in_container() {
61     List<RegisteredMigrationStep> steps = asList(registeredStepOf(1, MigrationStep1.class));
62
63     ((SpringComponentContainer) migrationContainer).startComponents();
64     try {
65       underTest.execute(steps);
66       fail("execute should have thrown a IllegalStateException");
67     } catch (IllegalStateException e) {
68       assertThat(e).hasMessage("Unable to load component " + MigrationStep1.class);
69     } finally {
70       assertThat(logTester.logs()).hasSize(2);
71       assertLogLevel(Level.INFO, "Executing DB migrations...");
72       assertLogLevel(Level.ERROR, "Executed DB migrations: failure | time=");
73     }
74   }
75
76   private void assertLogLevel(Level level, String... expected) {
77     List<String> logs = logTester.logs(level);
78     assertThat(logs).hasSize(expected.length);
79     Iterator<String> iterator = logs.iterator();
80     Arrays.stream(expected).forEachOrdered(log -> {
81       if (log.endsWith(" | time=")) {
82         assertThat(iterator.next()).startsWith(log);
83       } else {
84         assertThat(iterator.next()).isEqualTo(log);
85       }
86     });
87   }
88
89   @Test
90   public void execute_execute_the_instance_of_type_specified_in_step_in_stream_order() {
91     migrationContainer.add(MigrationStep1.class, MigrationStep2.class, MigrationStep3.class);
92     ((SpringComponentContainer) migrationContainer).startComponents();
93
94     underTest.execute(asList(
95       registeredStepOf(1, MigrationStep2.class),
96       registeredStepOf(2, MigrationStep1.class),
97       registeredStepOf(3, MigrationStep3.class)));
98
99     assertThat(SingleCallCheckerMigrationStep.calledSteps)
100       .containsExactly(MigrationStep2.class, MigrationStep1.class, MigrationStep3.class);
101     assertThat(logTester.logs()).hasSize(8);
102     assertLogLevel(Level.INFO,
103       "Executing DB migrations...",
104       "#1 '1-MigrationStep2'...",
105       "#1 '1-MigrationStep2': success | time=",
106       "#2 '2-MigrationStep1'...",
107       "#2 '2-MigrationStep1': success | time=",
108       "#3 '3-MigrationStep3'...",
109       "#3 '3-MigrationStep3': success | time=",
110       "Executed DB migrations: success | time=");
111
112     assertThat(migrationContainer.getComponentByType(MigrationStep1.class).isCalled()).isTrue();
113     assertThat(migrationContainer.getComponentByType(MigrationStep2.class).isCalled()).isTrue();
114     assertThat(migrationContainer.getComponentByType(MigrationStep3.class).isCalled()).isTrue();
115   }
116
117   @Test
118   public void execute_throws_MigrationStepExecutionException_on_first_failing_step_execution_throws_SQLException() {
119     migrationContainer.add(MigrationStep2.class, SqlExceptionFailingMigrationStep.class, MigrationStep3.class);
120     List<RegisteredMigrationStep> steps = asList(
121       registeredStepOf(1, MigrationStep2.class),
122       registeredStepOf(2, SqlExceptionFailingMigrationStep.class),
123       registeredStepOf(3, MigrationStep3.class));
124
125     ((SpringComponentContainer) migrationContainer).startComponents();
126     try {
127       underTest.execute(steps);
128       fail("a MigrationStepExecutionException should have been thrown");
129     } catch (MigrationStepExecutionException e) {
130       assertThat(e).hasMessage("Execution of migration step #2 '2-SqlExceptionFailingMigrationStep' failed");
131       assertThat(e).hasCause(SqlExceptionFailingMigrationStep.THROWN_EXCEPTION);
132     } finally {
133       assertThat(logTester.logs()).hasSize(6);
134       assertLogLevel(Level.INFO,
135         "Executing DB migrations...",
136         "#1 '1-MigrationStep2'...",
137         "#1 '1-MigrationStep2': success | time=",
138         "#2 '2-SqlExceptionFailingMigrationStep'...");
139       assertLogLevel(Level.ERROR,
140         "#2 '2-SqlExceptionFailingMigrationStep': failure | time=",
141         "Executed DB migrations: failure | time=");
142     }
143   }
144
145   @Test
146   public void execute_throws_MigrationStepExecutionException_on_first_failing_step_execution_throws_any_exception() {
147     migrationContainer.add(MigrationStep2.class, RuntimeExceptionFailingMigrationStep.class, MigrationStep3.class);
148
149     List<RegisteredMigrationStep> steps = asList(
150       registeredStepOf(1, MigrationStep2.class),
151       registeredStepOf(2, RuntimeExceptionFailingMigrationStep.class),
152       registeredStepOf(3, MigrationStep3.class));
153
154     ((SpringComponentContainer) migrationContainer).startComponents();
155     try {
156       underTest.execute(steps);
157       fail("should throw MigrationStepExecutionException");
158     } catch (MigrationStepExecutionException e) {
159       assertThat(e).hasMessage("Execution of migration step #2 '2-RuntimeExceptionFailingMigrationStep' failed");
160       assertThat(e.getCause()).isSameAs(RuntimeExceptionFailingMigrationStep.THROWN_EXCEPTION);
161     }
162   }
163
164   private static RegisteredMigrationStep registeredStepOf(int migrationNumber, Class<? extends MigrationStep> migrationStep1Class) {
165     return new RegisteredMigrationStep(migrationNumber, migrationNumber + "-" + migrationStep1Class.getSimpleName(), migrationStep1Class);
166   }
167
168   private static abstract class SingleCallCheckerMigrationStep implements MigrationStep {
169     private static List<Class<? extends MigrationStep>> calledSteps = new ArrayList<>();
170     private boolean called = false;
171
172     @Override
173     public void execute() {
174       checkState(!called, "execute must not be called twice");
175       this.called = true;
176       calledSteps.add(getClass());
177     }
178
179     public boolean isCalled() {
180       return called;
181     }
182
183     public static List<Class<? extends MigrationStep>> getCalledSteps() {
184       return calledSteps;
185     }
186   }
187
188   public static final class MigrationStep1 extends SingleCallCheckerMigrationStep {
189
190   }
191
192   public static final class MigrationStep2 extends SingleCallCheckerMigrationStep {
193
194   }
195
196   public static final class MigrationStep3 extends SingleCallCheckerMigrationStep {
197
198   }
199
200   public static class SqlExceptionFailingMigrationStep implements MigrationStep {
201     private static final SQLException THROWN_EXCEPTION = new SQLException("Faking SQL exception in MigrationStep#execute()");
202
203     @Override
204     public void execute() throws SQLException {
205       throw THROWN_EXCEPTION;
206     }
207   }
208
209   public static class RuntimeExceptionFailingMigrationStep implements MigrationStep {
210     private static final RuntimeException THROWN_EXCEPTION = new RuntimeException("Faking failing migration step");
211
212     @Override
213     public void execute() {
214       throw THROWN_EXCEPTION;
215     }
216   }
217
218 }