api project(':server:sonar-process')
api project(':sonar-core')
+ implementation project(':server:sonar-telemetry-core')
+
compileOnlyApi 'com.github.spotbugs:spotbugs-annotations'
testImplementation 'com.github.spotbugs:spotbugs-annotations'
import org.sonar.core.util.logs.Profiler;
import org.sonar.server.platform.db.migration.MutableDatabaseMigrationState;
import org.sonar.server.platform.db.migration.history.MigrationHistory;
+import org.sonar.server.telemetry.TelemetryDbMigrationSuccessProvider;
+import org.sonar.server.telemetry.TelemetryDbMigrationStepsProvider;
+import org.sonar.server.telemetry.TelemetryDbMigrationTotalTimeProvider;
import static com.google.common.base.Preconditions.checkState;
private final Container migrationContainer;
private final MigrationHistory migrationHistory;
private final MutableDatabaseMigrationState databaseMigrationState;
+ private final TelemetryDbMigrationTotalTimeProvider telemetryDbMigrationTotalTimeProvider;
+ private final TelemetryDbMigrationStepsProvider telemetryDbMigrationStepsProvider;
+ private final TelemetryDbMigrationSuccessProvider telemetryDbMigrationSuccessProvider;
- public MigrationStepsExecutorImpl(
- Container migrationContainer, MigrationHistory migrationHistory, MutableDatabaseMigrationState databaseMigrationState) {
+ public MigrationStepsExecutorImpl(Container migrationContainer, MigrationHistory migrationHistory, MutableDatabaseMigrationState databaseMigrationState,
+ TelemetryDbMigrationTotalTimeProvider telemetryDbMigrationTotalTimeProvider, TelemetryDbMigrationStepsProvider telemetryDbMigrationStepsProvider,
+ TelemetryDbMigrationSuccessProvider telemetryDbMigrationSuccessProvider) {
this.migrationContainer = migrationContainer;
this.migrationHistory = migrationHistory;
this.databaseMigrationState = databaseMigrationState;
+ this.telemetryDbMigrationTotalTimeProvider = telemetryDbMigrationTotalTimeProvider;
+ this.telemetryDbMigrationStepsProvider = telemetryDbMigrationStepsProvider;
+ this.telemetryDbMigrationSuccessProvider = telemetryDbMigrationSuccessProvider;
}
@Override
}
allStepsExecuted = true;
} finally {
+ long dbMigrationDuration = 0L;
if (allStepsExecuted) {
- globalProfiler.stopInfo(GLOBAL_END_MESSAGE,
+ dbMigrationDuration = globalProfiler.stopInfo(GLOBAL_END_MESSAGE,
databaseMigrationState.getCompletedMigrations(),
databaseMigrationState.getTotalMigrations(),
"success");
} else {
- globalProfiler.stopError(GLOBAL_END_MESSAGE,
+ dbMigrationDuration = globalProfiler.stopError(GLOBAL_END_MESSAGE,
databaseMigrationState.getCompletedMigrations(),
databaseMigrationState.getTotalMigrations(),
"failure");
}
+ telemetryDbMigrationTotalTimeProvider.setDbMigrationTotalTime(dbMigrationDuration);
+ telemetryDbMigrationStepsProvider.setDbMigrationCompletedSteps(databaseMigrationState.getCompletedMigrations());
+ telemetryDbMigrationSuccessProvider.setDbMigrationSuccess(allStepsExecuted);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.telemetry;
+
+import java.util.Optional;
+import org.sonar.telemetry.core.Dimension;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.TelemetryDataProvider;
+import org.sonar.telemetry.core.TelemetryDataType;
+
+public class TelemetryDbMigrationStepsProvider implements TelemetryDataProvider<Integer> {
+
+ private Integer dbMigrationCompletedSteps = null;
+
+ @Override
+ public String getMetricKey() {
+ return "db_migration_completed_steps";
+ }
+
+ @Override
+ public Granularity getGranularity() {
+ return Granularity.ADHOC;
+ }
+
+ @Override
+ public Dimension getDimension() {
+ return Dimension.INSTALLATION;
+ }
+
+ @Override
+ public TelemetryDataType getType() {
+ return TelemetryDataType.INTEGER;
+ }
+
+ public void setDbMigrationCompletedSteps(Integer dbMigrationCompletedSteps) {
+ this.dbMigrationCompletedSteps = dbMigrationCompletedSteps;
+ }
+
+ @Override
+ public Optional<Integer> getValue() {
+ return Optional.ofNullable(dbMigrationCompletedSteps);
+ }
+
+ @Override
+ public void destroy() {
+ dbMigrationCompletedSteps = null;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.telemetry;
+
+import java.util.Optional;
+import org.sonar.telemetry.core.Dimension;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.TelemetryDataProvider;
+import org.sonar.telemetry.core.TelemetryDataType;
+
+public class TelemetryDbMigrationSuccessProvider implements TelemetryDataProvider<Boolean> {
+
+ private Boolean dbMigrationSuccess = null;
+
+ @Override
+ public String getMetricKey() {
+ return "db_migration_success";
+ }
+
+ @Override
+ public TelemetryDataType getType() {
+ return TelemetryDataType.BOOLEAN;
+ }
+
+ @Override
+ public Granularity getGranularity() {
+ return Granularity.ADHOC;
+ }
+
+ @Override
+ public Dimension getDimension() {
+ return Dimension.INSTALLATION;
+ }
+
+ public void setDbMigrationSuccess(Boolean dbMigrationSuccess) {
+ this.dbMigrationSuccess = dbMigrationSuccess;
+ }
+
+ @Override
+ public Optional<Boolean> getValue() {
+ return Optional.ofNullable(dbMigrationSuccess);
+ }
+
+ @Override
+ public void destroy() {
+ dbMigrationSuccess = null;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.telemetry;
+
+import java.util.Optional;
+import org.sonar.telemetry.core.Dimension;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.TelemetryDataProvider;
+import org.sonar.telemetry.core.TelemetryDataType;
+
+public class TelemetryDbMigrationTotalTimeProvider implements TelemetryDataProvider<Long> {
+
+ private Long dbMigrationTotalTime = null;
+
+ @Override
+ public String getMetricKey() {
+ return "db_migration_total_time_ms";
+ }
+
+ @Override
+ public Granularity getGranularity() {
+ return Granularity.ADHOC;
+ }
+
+ @Override
+ public TelemetryDataType getType() {
+ return TelemetryDataType.INTEGER;
+ }
+
+ @Override
+ public Dimension getDimension() {
+ return Dimension.INSTALLATION;
+ }
+
+ public void setDbMigrationTotalTime(Long dbMigrationTotalTime) {
+ this.dbMigrationTotalTime = dbMigrationTotalTime;
+ }
+
+ @Override
+ public Optional<Long> getValue() {
+ return Optional.ofNullable(dbMigrationTotalTime);
+ }
+
+ @Override
+ public void destroy() {
+ dbMigrationTotalTime = null;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.telemetry;
+
+import javax.annotation.ParametersAreNonnullByDefault;
import org.sonar.server.platform.db.migration.step.MigrationSteps;
import org.sonar.server.platform.db.migration.step.MigrationStepsExecutorImpl;
import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;
+import org.sonar.server.telemetry.TelemetryDbMigrationSuccessProvider;
+import org.sonar.server.telemetry.TelemetryDbMigrationStepsProvider;
+import org.sonar.server.telemetry.TelemetryDbMigrationTotalTimeProvider;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
// add MigrationStepsExecutorImpl's dependencies
migrationContainer.add(mock(MigrationHistory.class));
migrationContainer.add(mock(MutableDatabaseMigrationState.class));
+ migrationContainer.add(mock(TelemetryDbMigrationStepsProvider.class));
+ migrationContainer.add(mock(TelemetryDbMigrationTotalTimeProvider.class));
+ migrationContainer.add(mock(TelemetryDbMigrationSuccessProvider.class));
migrationContainer.startComponents();
underTest.populateContainer(migrationContainer);
import org.sonar.server.platform.db.migration.step.MigrationSteps;
import org.sonar.server.platform.db.migration.step.NoOpMigrationStatusListener;
import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;
+import org.sonar.server.telemetry.TelemetryDbMigrationSuccessProvider;
+import org.sonar.server.telemetry.TelemetryDbMigrationStepsProvider;
+import org.sonar.server.telemetry.TelemetryDbMigrationTotalTimeProvider;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
private final SpringComponentContainer serverContainer = new SpringComponentContainer();
private final MigrationSteps migrationSteps = mock(MigrationSteps.class);
private final StepRegistry stepRegistry = new StepRegistry();
+ private final TelemetryDbMigrationTotalTimeProvider telemetryDbMigrationTotalTimeProvider = new TelemetryDbMigrationTotalTimeProvider();
+ private final TelemetryDbMigrationStepsProvider telemetryUpgradeStepsProvider = new TelemetryDbMigrationStepsProvider();
+ private final TelemetryDbMigrationSuccessProvider telemetryDbMigrationSuccessProvider = new TelemetryDbMigrationSuccessProvider();
private final MigrationEngineImpl underTest = new MigrationEngineImpl(migrationHistory, serverContainer, migrationSteps);
@BeforeEach
void before() {
+ serverContainer.add(telemetryDbMigrationTotalTimeProvider);
+ serverContainer.add(telemetryUpgradeStepsProvider);
+ serverContainer.add(telemetryDbMigrationSuccessProvider);
serverContainer.add(migrationSteps);
serverContainer.add(migrationHistory);
serverContainer.add(stepRegistry);
import org.sonar.server.platform.db.migration.engine.MigrationContainer;
import org.sonar.server.platform.db.migration.engine.SimpleMigrationContainer;
import org.sonar.server.platform.db.migration.history.MigrationHistory;
+import org.sonar.server.telemetry.TelemetryDbMigrationSuccessProvider;
+import org.sonar.server.telemetry.TelemetryDbMigrationStepsProvider;
+import org.sonar.server.telemetry.TelemetryDbMigrationTotalTimeProvider;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Arrays.asList;
@RegisterExtension
private final LogTesterJUnit5 logTester = new LogTesterJUnit5();
- private MigrationContainer migrationContainer = new SimpleMigrationContainer();
- private MigrationHistory migrationHistory = mock(MigrationHistory.class);
- private MutableDatabaseMigrationState databaseMigrationState = mock();
- private MigrationStepsExecutorImpl underTest = new MigrationStepsExecutorImpl(migrationContainer, migrationHistory, databaseMigrationState);
- private NoOpMigrationStatusListener migrationStatusListener = mock();
+ private final MigrationContainer migrationContainer = new SimpleMigrationContainer();
+ private final MigrationHistory migrationHistory = mock(MigrationHistory.class);
+ private final MutableDatabaseMigrationState databaseMigrationState = mock();
+ private final TelemetryDbMigrationTotalTimeProvider telemetryDbMigrationTotalTimeProvider = new TelemetryDbMigrationTotalTimeProvider();
+ private final TelemetryDbMigrationStepsProvider telemetryDbMigrationStepsProvider = new TelemetryDbMigrationStepsProvider();
+ private final TelemetryDbMigrationSuccessProvider telemetryDbMigrationSuccessProvider = new TelemetryDbMigrationSuccessProvider();
+ private final MigrationStepsExecutorImpl underTest = new MigrationStepsExecutorImpl(migrationContainer, migrationHistory, databaseMigrationState,
+ telemetryDbMigrationTotalTimeProvider, telemetryDbMigrationStepsProvider, telemetryDbMigrationSuccessProvider);
+ private final NoOpMigrationStatusListener migrationStatusListener = mock();
@BeforeEach
void setUp() {
((SpringComponentContainer) migrationContainer).startComponents();
underTest.execute(asList(
- registeredStepOf(1, MigrationStep2.class),
- registeredStepOf(2, MigrationStep1.class),
- registeredStepOf(3, MigrationStep3.class)),
+ registeredStepOf(1, MigrationStep2.class),
+ registeredStepOf(2, MigrationStep1.class),
+ registeredStepOf(3, MigrationStep3.class)),
migrationStatusListener);
assertThat(SingleCallCheckerMigrationStep.calledSteps)
}
}
+ @Test
+ void whenExecute_TelemetryDataIsProperlyAdded() {
+ migrationContainer.add(MigrationStep2.class, MigrationStep1.class, MigrationStep3.class);
+ when(databaseMigrationState.getCompletedMigrations()).thenReturn(3);
+
+ List<RegisteredMigrationStep> steps = asList(
+ registeredStepOf(1, MigrationStep2.class),
+ registeredStepOf(2, MigrationStep1.class),
+ registeredStepOf(3, MigrationStep3.class));
+
+ ((SpringComponentContainer) migrationContainer).startComponents();
+
+ underTest.execute(steps, migrationStatusListener);
+
+ assertThat(telemetryDbMigrationTotalTimeProvider.getValue().get()).isPositive();
+ assertThat(telemetryDbMigrationStepsProvider.getValue()).hasValue(3);
+ assertThat(telemetryDbMigrationSuccessProvider.getValue()).hasValue(true);
+
+ }
+
private static RegisteredMigrationStep registeredStepOf(int migrationNumber, Class<? extends MigrationStep> migrationStep1Class) {
return new RegisteredMigrationStep(migrationNumber, migrationNumber + "-" + migrationStep1Class.getSimpleName(), migrationStep1Class);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.telemetry;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.telemetry.core.Dimension.INSTALLATION;
+import static org.sonar.telemetry.core.Granularity.ADHOC;
+import static org.sonar.telemetry.core.TelemetryDataType.INTEGER;
+
+class TelemetryDbMigrationStepsProviderTest {
+
+ @Test
+ void testGetters() {
+ TelemetryDbMigrationStepsProvider underTest = new TelemetryDbMigrationStepsProvider();
+ underTest.setDbMigrationCompletedSteps(10);
+
+ assertThat(underTest.getMetricKey()).isEqualTo("db_migration_completed_steps");
+ assertThat(underTest.getGranularity()).isEqualTo(ADHOC);
+ assertThat(underTest.getDimension()).isEqualTo(INSTALLATION);
+ assertThat(underTest.getType()).isEqualTo(INTEGER);
+ assertThat(underTest.getValue()).contains(10);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.telemetry;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.telemetry.core.Dimension.INSTALLATION;
+import static org.sonar.telemetry.core.Granularity.ADHOC;
+import static org.sonar.telemetry.core.TelemetryDataType.BOOLEAN;
+
+class TelemetryDbMigrationSuccessProviderTest {
+
+ @Test
+ void testGetters() {
+ TelemetryDbMigrationSuccessProvider underTest = new TelemetryDbMigrationSuccessProvider();
+ underTest.setDbMigrationSuccess(true);
+
+ assertThat(underTest.getMetricKey()).isEqualTo("db_migration_success");
+ assertThat(underTest.getDimension()).isEqualTo(INSTALLATION);
+ assertThat(underTest.getType()).isEqualTo(BOOLEAN);
+ assertThat(underTest.getGranularity()).isEqualTo(ADHOC);
+ assertThat(underTest.getValue()).isPresent();
+ assertThat(underTest.getValue().get()).isTrue();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.telemetry;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.telemetry.core.Dimension.INSTALLATION;
+import static org.sonar.telemetry.core.Granularity.ADHOC;
+import static org.sonar.telemetry.core.TelemetryDataType.INTEGER;
+
+class TelemetryDbMigrationTotalTimeProviderTest {
+
+ @Test
+ void testGetters() {
+ TelemetryDbMigrationTotalTimeProvider underTest = new TelemetryDbMigrationTotalTimeProvider();
+ underTest.setDbMigrationTotalTime(100L);
+
+ assertThat(underTest.getMetricKey()).isEqualTo("db_migration_total_time_ms");
+ assertThat(underTest.getDimension()).isEqualTo(INSTALLATION);
+ assertThat(underTest.getType()).isEqualTo(INTEGER);
+ assertThat(underTest.getGranularity()).isEqualTo(ADHOC);
+ assertThat(underTest.getValue()).contains(100L);
+ }
+
+}
default Map<String, T> getUuidValues() {
throw new IllegalStateException("Not implemented");
}
+
+ default void destroy() {
+ // this method does nothing by default it is used to perform cleanup tasks if needed
+ }
}
import org.sonar.server.platform.db.migration.MigrationConfigurationModule;
import org.sonar.server.platform.db.migration.charset.DatabaseCharsetChecker;
import org.sonar.server.platform.db.migration.version.DatabaseVersion;
+import org.sonar.server.telemetry.TelemetryDbMigrationSuccessProvider;
+import org.sonar.server.telemetry.TelemetryDbMigrationStepsProvider;
+import org.sonar.server.telemetry.TelemetryDbMigrationTotalTimeProvider;
import org.sonar.server.platform.web.WebPagesCache;
import org.sonar.server.plugins.InstalledPluginReferentialFactory;
import org.sonar.server.plugins.PluginJarLoader;
// Migration state must be kept at level2 to survive moving in and then out of safe mode
// ExecutorService must be kept at level2 because stopping it when stopping safe mode level causes error making SQ fail
add(
+ TelemetryDbMigrationTotalTimeProvider.class,
+ TelemetryDbMigrationStepsProvider.class,
+ TelemetryDbMigrationSuccessProvider.class,
DatabaseMigrationStateImpl.class,
DatabaseMigrationExecutorServiceImpl.class);
verify(container).add(ServerPluginRepository.class);
verify(container).add(DatabaseCharsetChecker.class);
- verify(container, times(21)).add(any());
+ verify(container, times(24)).add(any());
}
@Test
verify(container).add(ServerPluginRepository.class);
verify(container, never()).add(DatabaseCharsetChecker.class);
- verify(container, times(19)).add(any());
+ verify(container, times(22)).add(any());
}