From ba30541afac7e59d5e910364b9d94c33d3b3dff9 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Tue, 21 Nov 2017 15:33:39 +0100 Subject: [PATCH] SONAR-10087 Populate built-in flag on existing quality gates --- .../org/sonar/db/version/schema-h2.ddl | 2 +- .../db/migration/version/v70/DbVersion70.java | 5 +- .../MakeQualityGatesIsBuiltInNotNullable.java | 48 +++++++ .../v70/PopulateQualityGatesIsBuiltIn.java | 55 ++++++++ .../v70/AddIsBuiltInToQualityGatesTest.java | 2 +- .../version/v70/DbVersion70Test.java | 2 +- ...eQualityGatesIsBuiltInNotNullableTest.java | 57 ++++++++ .../PopulateQualityGatesIsBuiltInTest.java | 123 ++++++++++++++++++ ...uality_gates_7_0.sql => quality_gates.sql} | 0 .../quality_gates.sql | 8 ++ .../quality_gates.sql | 8 ++ .../qualitygate/QualityGateUpdater.java | 2 +- .../server/qualitygate/QualityGates.java | 2 +- .../qualitygate/QualityGateUpdaterTest.java | 1 + 14 files changed, 309 insertions(+), 6 deletions(-) create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullable.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltIn.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullableTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltInTest.java rename server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/AddIsBuiltInToQualityGatesTest/{quality_gates_7_0.sql => quality_gates.sql} (100%) create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullableTest/quality_gates.sql create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltInTest/quality_gates.sql diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl index 733e8d5f09d..9ad599b91fd 100644 --- a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -229,7 +229,7 @@ CREATE UNIQUE INDEX "EVENTS_UUID" ON "EVENTS" ("UUID"); CREATE TABLE "QUALITY_GATES" ( "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), "NAME" VARCHAR(100) NOT NULL, - "IS_BUILT_IN" BOOLEAN NULL, + "IS_BUILT_IN" BOOLEAN NOT NULL, "CREATED_AT" TIMESTAMP, "UPDATED_AT" TIMESTAMP, ); diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/DbVersion70.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/DbVersion70.java index 48c677f007e..b8bae431e05 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/DbVersion70.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/DbVersion70.java @@ -27,7 +27,10 @@ public class DbVersion70 implements DbVersion { @Override public void addSteps(MigrationStepRegistry registry) { registry - .add(1900, "Add QUALITY_GATES.IS_BUILT_IN", AddIsBuiltInToQualityGates.class); + .add(1900, "Add QUALITY_GATES.IS_BUILT_IN", AddIsBuiltInToQualityGates.class) + .add(1901, "Populate QUALITY_GATES.IS_BUILT_IN", PopulateQualityGatesIsBuiltIn.class) + .add(1902, "Make QUALITY_GATES.IS_BUILT_IN not null", MakeQualityGatesIsBuiltInNotNullable.class) + ; } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullable.java new file mode 100644 index 00000000000..723a93e32a4 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullable.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.platform.db.migration.version.v70; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.def.BooleanColumnDef; +import org.sonar.server.platform.db.migration.sql.AlterColumnsBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.BooleanColumnDef.newBooleanColumnDefBuilder; + +public class MakeQualityGatesIsBuiltInNotNullable extends DdlChange { + + public MakeQualityGatesIsBuiltInNotNullable(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + BooleanColumnDef column = newBooleanColumnDefBuilder() + .setColumnName("is_built_in") + .setIsNullable(false) + .build(); + + context.execute(new AlterColumnsBuilder(getDialect(), "quality_gates") + .updateColumn(column) + .build()); + } + +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltIn.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltIn.java new file mode 100644 index 00000000000..b2a4480d3ab --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltIn.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.platform.db.migration.version.v70; + +import java.sql.SQLException; +import java.util.Date; +import org.sonar.api.utils.System2; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; + +public class PopulateQualityGatesIsBuiltIn extends DataChange { + + private static final String SONARQUBE_WAY_QUALITY_GATE = "SonarQube way"; + + private final System2 system2; + + public PopulateQualityGatesIsBuiltIn(Database db, System2 system2) { + super(db); + this.system2 = system2; + } + + @Override + public void execute(Context context) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select("select id, name from quality_gates where is_built_in is null"); + massUpdate.rowPluralName("quality_gates"); + massUpdate.update("update quality_gates set is_built_in=?, updated_at=? where id=?"); + massUpdate.execute((row, update) -> { + String name = row.getString(2); + update.setBoolean(1, SONARQUBE_WAY_QUALITY_GATE.equals(name)); + update.setDate(2, new Date(system2.now())); + update.setLong(3, row.getLong(1)); + return true; + }); + } + +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/AddIsBuiltInToQualityGatesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/AddIsBuiltInToQualityGatesTest.java index 3cdfb04724b..f8d66df8e4c 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/AddIsBuiltInToQualityGatesTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/AddIsBuiltInToQualityGatesTest.java @@ -30,7 +30,7 @@ import static java.sql.Types.BOOLEAN; public class AddIsBuiltInToQualityGatesTest { @Rule - public final CoreDbTester dbTester = CoreDbTester.createForSchema(AddIsBuiltInToQualityGatesTest.class, "quality_gates_7_0.sql"); + public final CoreDbTester dbTester = CoreDbTester.createForSchema(AddIsBuiltInToQualityGatesTest.class, "quality_gates.sql"); @Rule public ExpectedException expectedException = ExpectedException.none(); diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/DbVersion70Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/DbVersion70Test.java index f28d675304d..4daa8d55926 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/DbVersion70Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/DbVersion70Test.java @@ -35,7 +35,7 @@ public class DbVersion70Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 1); + verifyMigrationCount(underTest, 3); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullableTest.java new file mode 100644 index 00000000000..72faa61a266 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullableTest.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.platform.db.migration.version.v70; + +import java.sql.SQLException; +import java.sql.Types; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.CoreDbTester; + +public class MakeQualityGatesIsBuiltInNotNullableTest { + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(MakeQualityGatesIsBuiltInNotNullableTest.class, "quality_gates.sql"); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private MakeQualityGatesIsBuiltInNotNullable underTest = new MakeQualityGatesIsBuiltInNotNullable(db.database()); + + @Test + public void execute_makes_column_not_null() throws SQLException { + db.assertColumnDefinition("quality_gates", "is_built_in", Types.BOOLEAN, null, true); + insertRow(1); + insertRow(2); + + underTest.execute(); + + db.assertColumnDefinition("quality_gates", "is_built_in", Types.BOOLEAN, null, false); + } + + private void insertRow(int id) { + db.executeInsert( + "QUALITY_GATES", + "NAME", "name_" + id, + "IS_BUILT_IN", false + ); + } + +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltInTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltInTest.java new file mode 100644 index 00000000000..af090f02616 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltInTest.java @@ -0,0 +1,123 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.platform.db.migration.version.v70; + +import java.sql.SQLException; +import java.util.Date; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.assertj.core.groups.Tuple; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.internal.TestSystem2; +import org.sonar.db.CoreDbTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +public class PopulateQualityGatesIsBuiltInTest { + + private final static long PAST = 10_000_000_000L; + private final static long NOW = 50_000_000_000L; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(PopulateQualityGatesIsBuiltInTest.class, "quality_gates.sql"); + + private System2 system2 = new TestSystem2().setNow(NOW); + + private PopulateQualityGatesIsBuiltIn underTest = new PopulateQualityGatesIsBuiltIn(db.database(), system2); + + @Test + public void has_no_effect_if_table_is_empty() throws SQLException { + underTest.execute(); + + assertThat(db.countRowsOfTable("quality_gates")).isEqualTo(0); + } + + @Test + public void updates_sonarqube_way_is_build_in_column_to_true() throws SQLException { + insertQualityGate("SonarQube way", null); + + underTest.execute(); + + assertQualityGates(tuple("SonarQube way", true, new Date(PAST), new Date(NOW))); + } + + @Test + public void updates_none_sonarqube_way_is_build_in_column_to_false() throws SQLException { + insertQualityGate("Other 1", null); + insertQualityGate("Other 2", null); + + underTest.execute(); + + assertQualityGates( + tuple("Other 1", false, new Date(PAST), new Date(NOW)), + tuple("Other 2", false, new Date(PAST), new Date(NOW))); + } + + @Test + public void does_nothing_when_built_in_column_is_set() throws SQLException { + insertQualityGate("SonarQube way", true); + insertQualityGate("Other way", false); + + underTest.execute(); + + assertQualityGates( + tuple("SonarQube way", true, new Date(PAST), new Date(PAST)), + tuple("Other way", false, new Date(PAST), new Date(PAST))); + } + + @Test + public void execute_is_reentreant() throws SQLException { + insertQualityGate("SonarQube way", null); + insertQualityGate("Other way", null); + + underTest.execute(); + + underTest.execute(); + + assertQualityGates( + tuple("SonarQube way", true, new Date(PAST), new Date(NOW)), + tuple("Other way", false, new Date(PAST), new Date(NOW))); + } + + private void assertQualityGates(Tuple... expectedTuples) { + assertThat(db.select("SELECT NAME, IS_BUILT_IN, CREATED_AT, UPDATED_AT FROM QUALITY_GATES") + .stream() + .map(map -> new Tuple(map.get("NAME"), map.get("IS_BUILT_IN"), map.get("CREATED_AT"), map.get("UPDATED_AT"))) + .collect(Collectors.toList())) + .containsExactlyInAnyOrder(expectedTuples); + } + + private void insertQualityGate(String name, @Nullable Boolean builtIn) { + db.executeInsert( + "QUALITY_GATES", + "NAME", name, + "IS_BUILT_IN", builtIn == null ? null : builtIn.toString(), + "CREATED_AT", new Date(PAST), + "UPDATED_AT", new Date(PAST)); + } + +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/AddIsBuiltInToQualityGatesTest/quality_gates_7_0.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/AddIsBuiltInToQualityGatesTest/quality_gates.sql similarity index 100% rename from server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/AddIsBuiltInToQualityGatesTest/quality_gates_7_0.sql rename to server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/AddIsBuiltInToQualityGatesTest/quality_gates.sql diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullableTest/quality_gates.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullableTest/quality_gates.sql new file mode 100644 index 00000000000..52b9423f526 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/MakeQualityGatesIsBuiltInNotNullableTest/quality_gates.sql @@ -0,0 +1,8 @@ +CREATE TABLE "QUALITY_GATES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "NAME" VARCHAR(100) NOT NULL, + "IS_BUILT_IN" BOOLEAN NULL, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, +); +CREATE UNIQUE INDEX "UNIQ_QUALITY_GATES" ON "QUALITY_GATES" ("NAME"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltInTest/quality_gates.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltInTest/quality_gates.sql new file mode 100644 index 00000000000..52b9423f526 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v70/PopulateQualityGatesIsBuiltInTest/quality_gates.sql @@ -0,0 +1,8 @@ +CREATE TABLE "QUALITY_GATES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "NAME" VARCHAR(100) NOT NULL, + "IS_BUILT_IN" BOOLEAN NULL, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, +); +CREATE UNIQUE INDEX "UNIQ_QUALITY_GATES" ON "QUALITY_GATES" ("NAME"); diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateUpdater.java index 6b96732a6bf..722e593c586 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateUpdater.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateUpdater.java @@ -41,7 +41,7 @@ public class QualityGateUpdater { public QualityGateDto create(DbSession dbSession, String name) { validateQualityGate(dbSession, null, name); - QualityGateDto newQualityGate = new QualityGateDto().setName(name); + QualityGateDto newQualityGate = new QualityGateDto().setName(name).setBuiltIn(false); dbClient.qualityGateDao().insert(dbSession, newQualityGate); return newQualityGate; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java index fce67bf44a4..add13b7b5c4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java @@ -101,7 +101,7 @@ public class QualityGates { getNonNullQgate(sourceId); try (DbSession dbSession = dbClient.openSession(false)) { validateQualityGate(dbSession, null, destinationName); - QualityGateDto destinationGate = new QualityGateDto().setName(destinationName); + QualityGateDto destinationGate = new QualityGateDto().setName(destinationName).setBuiltIn(false); dao.insert(dbSession, destinationGate); for (QualityGateConditionDto sourceCondition : conditionDao.selectForQualityGate(dbSession, sourceId)) { conditionDao.insert(new QualityGateConditionDto().setQualityGateId(destinationGate.getId()) diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateUpdaterTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateUpdaterTest.java index 4b9e66207f7..accccda8eaf 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateUpdaterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateUpdaterTest.java @@ -52,6 +52,7 @@ public class QualityGateUpdaterTest { assertThat(result).isNotNull(); assertThat(result.getName()).isEqualTo(QGATE_NAME); assertThat(result.getCreatedAt()).isNotNull(); + assertThat(result.isBuiltIn()).isFalse(); QualityGateDto reloaded = dbClient.qualityGateDao().selectByName(dbSession, QGATE_NAME); assertThat(reloaded).isNotNull(); } -- 2.39.5