]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9803 add the tests that have been dropped by mistake
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 28 Sep 2017 09:01:30 +0000 (11:01 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 28 Sep 2017 09:21:23 +0000 (11:21 +0200)
server/sonar-process/src/test/java/org/sonar/process/LoggingRule.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/TestLogbackAppender.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/cluster/NodeTypeTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/cluster/health/HealthStateRefresherTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/cluster/health/NodeDetailsTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/cluster/health/NodeDetailsTestSupport.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/cluster/health/NodeHealthTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/cluster/health/SharedHealthStateImplTest.java [new file with mode: 0644]
server/sonar-process/src/test/resources/org/sonar/process/logback-test.xml [new file with mode: 0644]

diff --git a/server/sonar-process/src/test/java/org/sonar/process/LoggingRule.java b/server/sonar-process/src/test/java/org/sonar/process/LoggingRule.java
new file mode 100644 (file)
index 0000000..00e8662
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.process;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.rules.ExternalResource;
+import org.slf4j.LoggerFactory;
+import org.slf4j.event.Level;
+import org.sonar.process.logging.LogbackHelper;
+
+public class LoggingRule extends ExternalResource {
+
+  private final Class loggerClass;
+
+  public LoggingRule(Class loggerClass) {
+    this.loggerClass = loggerClass;
+  }
+
+  @Override
+  protected void before() throws Throwable {
+    new LogbackHelper().resetFromXml("/org/sonar/process/logback-test.xml");
+    TestLogbackAppender.events.clear();
+    setLevel(Level.INFO);
+  }
+
+  @Override
+  protected void after() {
+    TestLogbackAppender.events.clear();
+    setLevel(Level.INFO);
+  }
+
+  public LoggingRule setLevel(Level level) {
+    Logger logbackLogger = (Logger) LoggerFactory.getLogger(loggerClass);
+    ch.qos.logback.classic.Level l = ch.qos.logback.classic.Level.valueOf(level.name());
+    logbackLogger.setLevel(l);
+    return this;
+  }
+
+  public List<String> getLogs() {
+    return TestLogbackAppender.events.stream()
+      .map(LoggingEvent::getFormattedMessage)
+      .collect(Collectors.toList());
+  }
+
+  public List<String> getLogs(Level level) {
+    return TestLogbackAppender.events.stream()
+      .filter(e -> e.getLoggerName().equals(loggerClass.getName()))
+      .filter(e -> e.getLevel().levelStr.equals(level.name()))
+      .map(LoggingEvent::getFormattedMessage)
+      .collect(Collectors.toList());
+  }
+
+  public boolean hasLog(Level level, String message) {
+    return TestLogbackAppender.events.stream()
+      .filter(e -> e.getLevel().levelStr.equals(level.name()))
+      .anyMatch(e -> e.getFormattedMessage().equals(message));
+  }
+
+  public boolean hasLog(String message) {
+    return TestLogbackAppender.events.stream()
+      .anyMatch(e -> e.getFormattedMessage().equals(message));
+  }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/TestLogbackAppender.java b/server/sonar-process/src/test/java/org/sonar/process/TestLogbackAppender.java
new file mode 100644 (file)
index 0000000..b38e23c
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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.process;
+
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestLogbackAppender extends AppenderBase<LoggingEvent> {
+  static List<LoggingEvent> events = new ArrayList<>();
+
+  @Override
+  protected void append(LoggingEvent e) {
+    events.add(e);
+  }
+
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/cluster/NodeTypeTest.java b/server/sonar-process/src/test/java/org/sonar/process/cluster/NodeTypeTest.java
new file mode 100644 (file)
index 0000000..c21a06f
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.process.cluster;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NodeTypeTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void test_parse() {
+    assertThat(NodeType.parse("application")).isEqualTo(NodeType.APPLICATION);
+    assertThat(NodeType.parse("search")).isEqualTo(NodeType.SEARCH);
+  }
+
+  @Test
+  public void parse_an_unknown_value_must_throw_IAE() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Invalid value: XYZ");
+
+    NodeType.parse("XYZ");
+  }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/cluster/health/HealthStateRefresherTest.java b/server/sonar-process/src/test/java/org/sonar/process/cluster/health/HealthStateRefresherTest.java
new file mode 100644 (file)
index 0000000..61baa7d
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 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.process.cluster.health;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
+
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class HealthStateRefresherTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private Random random = new Random();
+  private NodeDetailsTestSupport testSupport = new NodeDetailsTestSupport(random);
+
+  private HealthStateRefresherExecutorService executorService = mock(HealthStateRefresherExecutorService.class);
+  private NodeHealthProvider nodeHealthProvider = mock(NodeHealthProvider.class);
+  private SharedHealthState sharedHealthState = mock(SharedHealthState.class);
+  private HealthStateRefresher underTest = new HealthStateRefresher(executorService, nodeHealthProvider, sharedHealthState);
+
+  @Test
+  public void start_adds_runnable_with_10_second_delay_and_initial_delay_putting_NodeHealth_from_provider_into_SharedHealthState() {
+    ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+    NodeHealth[] nodeHealths = {
+      testSupport.randomNodeHealth(),
+      testSupport.randomNodeHealth(),
+      testSupport.randomNodeHealth()
+    };
+    Error expected = new Error("Simulating exception raised by NodeHealthProvider");
+    when(nodeHealthProvider.get())
+      .thenReturn(nodeHealths[0])
+      .thenReturn(nodeHealths[1])
+      .thenReturn(nodeHealths[2])
+      .thenThrow(expected);
+
+    underTest.start();
+
+    verify(executorService).scheduleWithFixedDelay(runnableCaptor.capture(), eq(1L), eq(10L), eq(TimeUnit.SECONDS));
+
+    Runnable runnable = runnableCaptor.getValue();
+    runnable.run();
+    runnable.run();
+    runnable.run();
+
+    verify(sharedHealthState).writeMine(nodeHealths[0]);
+    verify(sharedHealthState).writeMine(nodeHealths[1]);
+    verify(sharedHealthState).writeMine(nodeHealths[2]);
+
+    try {
+      runnable.run();
+    } catch (IllegalStateException e) {
+      fail("Runnable should catch any Throwable");
+    }
+  }
+
+  @Test
+  public void stop_has_no_effect() {
+    underTest.stop();
+
+    verify(sharedHealthState).clearMine();
+    verifyZeroInteractions(executorService, nodeHealthProvider);
+  }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/cluster/health/NodeDetailsTest.java b/server/sonar-process/src/test/java/org/sonar/process/cluster/health/NodeDetailsTest.java
new file mode 100644 (file)
index 0000000..9e15e2b
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * 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.process.cluster.health;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.cluster.health.NodeDetails.newNodeDetailsBuilder;
+
+public class NodeDetailsTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private Random random = new Random();
+  private NodeDetailsTestSupport testSupport = new NodeDetailsTestSupport(random);
+  private NodeDetails.Type randomType = testSupport.randomType();
+  private NodeDetails.Builder builderUnderTest = newNodeDetailsBuilder();
+
+  @Test
+  public void setType_throws_NPE_if_arg_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("type can't be null");
+
+    builderUnderTest.setType(null);
+  }
+
+  @Test
+  public void setName_throws_NPE_if_arg_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("name can't be null");
+
+    builderUnderTest.setName(null);
+  }
+
+  @Test
+  public void setName_throws_IAE_if_arg_is_empty() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("name can't be empty");
+
+    builderUnderTest.setName("");
+  }
+
+  @Test
+  public void setName_throws_IAE_if_arg_is_empty_after_trim() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("name can't be empty");
+
+    builderUnderTest.setName("  ");
+  }
+
+  @Test
+  public void setHost_throws_NPE_if_arg_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("host can't be null");
+
+    builderUnderTest.setHost(null);
+  }
+
+  @Test
+  public void setHost_throws_IAE_if_arg_is_empty() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("host can't be empty");
+
+    builderUnderTest.setHost("");
+  }
+
+  @Test
+  public void setHost_throws_IAE_if_arg_is_empty_after_trim() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("host can't be empty");
+
+    builderUnderTest.setHost("  ");
+  }
+
+  @Test
+  public void setPort_throws_IAE_if_arg_is_less_than_1() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("port must be > 0");
+
+    builderUnderTest.setPort(-random.nextInt(5));
+  }
+
+  @Test
+  public void setStarted_throws_IAE_if_arg_is_less_than_1() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("startedAt must be > 0");
+
+    builderUnderTest.setStartedAt(-random.nextInt(5));
+  }
+
+  @Test
+  public void build_throws_NPE_if_type_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("type can't be null");
+
+    builderUnderTest.build();
+  }
+
+  @Test
+  public void build_throws_NPE_if_name_is_null() {
+    builderUnderTest
+      .setType(randomType);
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("name can't be null");
+
+    builderUnderTest.build();
+  }
+
+  @Test
+  public void build_throws_NPE_if_host_is_null() {
+    builderUnderTest
+      .setType(randomType)
+      .setName(randomAlphanumeric(2));
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("host can't be null");
+
+    builderUnderTest.build();
+  }
+
+  @Test
+  public void build_throws_IAE_if_setPort_not_called() {
+    builderUnderTest
+      .setType(randomType)
+      .setName(randomAlphanumeric(2))
+      .setHost(randomAlphanumeric(3));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("port must be > 0");
+
+    builderUnderTest.build();
+  }
+
+  @Test
+  public void build_throws_IAE_if_setStarted_not_called() {
+    builderUnderTest
+      .setType(randomType)
+      .setName(randomAlphanumeric(2))
+      .setHost(randomAlphanumeric(3))
+      .setPort(1 + random.nextInt(33));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("startedAt must be > 0");
+
+    builderUnderTest.build();
+  }
+
+  @Test
+  public void equals_is_based_on_content() {
+    NodeDetails.Builder builder = testSupport.randomNodeDetailsBuilder();
+
+    NodeDetails underTest = builder.build();
+
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(builder.build())
+      .isEqualTo(underTest)
+      .isNotSameAs(underTest);
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+  }
+
+  @Test
+  public void hashcode_is_based_on_content() {
+    NodeDetails.Builder builder = testSupport.randomNodeDetailsBuilder();
+
+    NodeDetails underTest = builder.build();
+
+    assertThat(builder.build().hashCode())
+      .isEqualTo(underTest.hashCode());
+  }
+
+  @Test
+  public void NodeDetails_is_Externalizable() throws IOException, ClassNotFoundException {
+    NodeDetails source = testSupport.randomNodeDetails();
+    byte[] byteArray = testSupport.serialize(source);
+
+    NodeDetails underTest = (NodeDetails) new ObjectInputStream(new ByteArrayInputStream(byteArray)).readObject();
+
+    assertThat(underTest).isEqualTo(source);
+  }
+
+  @Test
+  public void verify_toString() {
+    String name = randomAlphanumeric(3);
+    String host = randomAlphanumeric(10);
+    int port = 1 + random.nextInt(10);
+    long startedAt = 1 + random.nextInt(666);
+
+    NodeDetails underTest = builderUnderTest
+      .setType(randomType)
+      .setName(name)
+      .setHost(host)
+      .setPort(port)
+      .setStartedAt(startedAt)
+      .build();
+
+    assertThat(underTest.toString())
+      .isEqualTo("NodeDetails{type=" + randomType + ", name='" + name + "', host='" + host + "', port=" + port + ", startedAt=" + startedAt + "}");
+  }
+
+  @Test
+  public void verify_getters() {
+    String name = randomAlphanumeric(3);
+    String host = randomAlphanumeric(10);
+    int port = 1 + random.nextInt(10);
+    long startedAt = 1 + random.nextInt(666);
+
+    NodeDetails underTest = builderUnderTest
+      .setType(randomType)
+      .setName(name)
+      .setHost(host)
+      .setPort(port)
+      .setStartedAt(startedAt)
+      .build();
+
+    assertThat(underTest.getType()).isEqualTo(randomType);
+    assertThat(underTest.getName()).isEqualTo(name);
+    assertThat(underTest.getHost()).isEqualTo(host);
+    assertThat(underTest.getPort()).isEqualTo(port);
+    assertThat(underTest.getStartedAt()).isEqualTo(startedAt);
+  }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/cluster/health/NodeDetailsTestSupport.java b/server/sonar-process/src/test/java/org/sonar/process/cluster/health/NodeDetailsTestSupport.java
new file mode 100644 (file)
index 0000000..b0448ef
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.process.cluster.health;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+import java.util.stream.IntStream;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.sonar.process.cluster.health.NodeDetails.newNodeDetailsBuilder;
+import static org.sonar.process.cluster.health.NodeHealth.newNodeHealthBuilder;
+
+public class NodeDetailsTestSupport {
+  private final Random random;
+
+  public NodeDetailsTestSupport() {
+    this(new Random());
+  }
+
+  NodeDetailsTestSupport(Random random) {
+    this.random = random;
+  }
+
+  NodeHealth.Status randomStatus() {
+    return NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)];
+  }
+
+  NodeHealth randomNodeHealth() {
+    return randomBuilder().build();
+  }
+
+  NodeHealth.Builder randomBuilder() {
+    return randomBuilder(0);
+  }
+
+  NodeHealth.Builder randomBuilder(int minCauseCount) {
+    NodeHealth.Builder builder = newNodeHealthBuilder()
+      .setStatus(randomStatus())
+      .setDetails(randomNodeDetails());
+    IntStream.range(0, minCauseCount + random.nextInt(2)).mapToObj(i -> randomAlphanumeric(4)).forEach(builder::addCause);
+    return builder;
+  }
+
+  NodeDetails randomNodeDetails() {
+    return randomNodeDetailsBuilder()
+      .build();
+  }
+
+  NodeDetails.Builder randomNodeDetailsBuilder() {
+    return newNodeDetailsBuilder()
+      .setType(randomType())
+      .setName(randomAlphanumeric(3))
+      .setHost(randomAlphanumeric(10))
+      .setPort(1 + random.nextInt(10))
+      .setStartedAt(1 + random.nextInt(666));
+  }
+
+  NodeDetails.Type randomType() {
+    return NodeDetails.Type.values()[random.nextInt(NodeDetails.Type.values().length)];
+  }
+
+  static byte[] serialize(Object source) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(out)) {
+      objectOutputStream.writeObject(source);
+    }
+    return out.toByteArray();
+  }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/cluster/health/NodeHealthTest.java b/server/sonar-process/src/test/java/org/sonar/process/cluster/health/NodeHealthTest.java
new file mode 100644 (file)
index 0000000..2652d6d
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * 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.process.cluster.health;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.cluster.health.NodeHealth.newNodeHealthBuilder;
+
+public class NodeHealthTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private Random random = new Random();
+  private NodeDetailsTestSupport testSupport = new NodeDetailsTestSupport(random);
+  private NodeHealth.Status randomStatus = testSupport.randomStatus();
+  private NodeHealth.Builder builderUnderTest = newNodeHealthBuilder();
+
+  @Test
+  public void setStatus_throws_NPE_if_arg_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can't be null");
+
+    builderUnderTest.setStatus(null);
+  }
+
+  @Test
+  public void setDetails_throws_NPE_if_arg_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("details can't be null");
+
+    builderUnderTest.setDetails(null);
+  }
+
+  @Test
+  public void build_throws_NPE_if_status_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can't be null");
+
+    builderUnderTest.build();
+  }
+
+  @Test
+  public void build_throws_NPE_if_details_is_null() {
+    builderUnderTest.setStatus(randomStatus);
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("details can't be null");
+
+    builderUnderTest.build();
+  }
+
+  @Test
+  public void clearClauses_clears_clauses_of_builder() {
+    NodeHealth.Builder underTest = testSupport.randomBuilder();
+    NodeHealth original = underTest
+      .addCause(randomAlphanumeric(3))
+      .build();
+
+    underTest.clearCauses();
+
+    NodeHealth second = underTest.build();
+    assertThat(second.getStatus()).isEqualTo(original.getStatus());
+    assertThat(second.getDetails()).isEqualTo(original.getDetails());
+    assertThat(second.getCauses()).isEmpty();
+  }
+
+  @Test
+  public void builder_can_be_reused() {
+    NodeHealth.Builder builder = testSupport.randomBuilder(1);
+    NodeHealth original = builder.build();
+    NodeHealth second = builder.build();
+
+    NodeHealth.Status newRandomStatus = NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)];
+    NodeDetails newNodeDetails = testSupport.randomNodeDetails();
+    builder
+      .clearCauses()
+      .setStatus(newRandomStatus)
+      .setDetails(newNodeDetails);
+    String[] newCauses = IntStream.range(0, 1 + random.nextInt(2)).mapToObj(i -> randomAlphanumeric(4)).toArray(String[]::new);
+    Arrays.stream(newCauses).forEach(builder::addCause);
+
+    NodeHealth newNodeHealth = builder.build();
+
+    assertThat(second).isEqualTo(original);
+    assertThat(newNodeHealth.getStatus()).isEqualTo(newRandomStatus);
+    assertThat(newNodeHealth.getDetails()).isEqualTo(newNodeDetails);
+    assertThat(newNodeHealth.getCauses()).containsOnly(newCauses);
+  }
+
+  @Test
+  public void equals_is_based_on_content() {
+    NodeHealth.Builder builder = testSupport.randomBuilder();
+
+    NodeHealth underTest = builder.build();
+
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(builder.build())
+      .isEqualTo(underTest)
+      .isNotSameAs(underTest);
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+  }
+
+  @Test
+  public void hashcode_is_based_on_content() {
+    NodeHealth.Builder builder = testSupport.randomBuilder();
+
+    NodeHealth underTest = builder.build();
+
+    assertThat(builder.build().hashCode())
+      .isEqualTo(underTest.hashCode());
+  }
+
+  @Test
+  public void class_is_serializable_with_causes() throws IOException, ClassNotFoundException {
+    NodeHealth source = testSupport.randomBuilder(1).build();
+    byte[] bytes = testSupport.serialize(source);
+
+    NodeHealth underTest = (NodeHealth) new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
+
+    assertThat(underTest).isEqualTo(source);
+  }
+
+  @Test
+  public void class_is_serializable_without_causes() throws IOException, ClassNotFoundException {
+    NodeHealth.Builder builder = newNodeHealthBuilder()
+      .setStatus(randomStatus)
+      .setDetails(testSupport.randomNodeDetails());
+    NodeHealth source = builder.build();
+    byte[] bytes = testSupport.serialize(source);
+
+    NodeHealth underTest = (NodeHealth) new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
+
+    assertThat(underTest).isEqualTo(source);
+  }
+
+  @Test
+  public void verify_toString() {
+    NodeDetails nodeDetails = testSupport.randomNodeDetails();
+    String cause = randomAlphanumeric(4);
+    NodeHealth.Builder builder = builderUnderTest
+      .setStatus(randomStatus)
+      .setDetails(nodeDetails)
+      .addCause(cause);
+
+    NodeHealth underTest = builder.build();
+
+    assertThat(underTest.toString())
+      .isEqualTo("NodeHealth{status=" + randomStatus + ", causes=[" + cause + "], details=" + nodeDetails + "}");
+  }
+
+  @Test
+  public void verify_getters() {
+    NodeDetails nodeDetails = testSupport.randomNodeDetails();
+    NodeHealth.Builder builder = builderUnderTest
+      .setStatus(randomStatus)
+      .setDetails(nodeDetails);
+    String[] causes = IntStream.range(0, random.nextInt(10)).mapToObj(i -> randomAlphanumeric(4)).toArray(String[]::new);
+    Arrays.stream(causes).forEach(builder::addCause);
+
+    NodeHealth underTest = builder.build();
+
+    assertThat(underTest.getStatus()).isEqualTo(randomStatus);
+    assertThat(underTest.getDetails()).isEqualTo(nodeDetails);
+    assertThat(underTest.getCauses()).containsOnly(causes);
+  }
+
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/cluster/health/SharedHealthStateImplTest.java b/server/sonar-process/src/test/java/org/sonar/process/cluster/health/SharedHealthStateImplTest.java
new file mode 100644 (file)
index 0000000..0ce08ea
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * 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.process.cluster.health;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.slf4j.event.Level;
+import org.sonar.process.LoggingRule;
+import org.sonar.process.cluster.hz.HazelcastMember;
+
+import static java.util.Collections.singleton;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.cluster.health.NodeDetails.newNodeDetailsBuilder;
+import static org.sonar.process.cluster.health.NodeHealth.newNodeHealthBuilder;
+
+public class SharedHealthStateImplTest {
+  private static final String MAP_SQ_HEALTH_STATE = "sq_health_state";
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public LoggingRule logging = new LoggingRule(SharedHealthStateImpl.class);
+
+  private final Random random = new Random();
+  private long clusterTime = 99 + Math.abs(random.nextInt(9621));
+  private HazelcastMember hazelcastMember = mock(HazelcastMember.class);
+  private SharedHealthStateImpl underTest = new SharedHealthStateImpl(hazelcastMember);
+
+  @Test
+  public void write_fails_with_NPE_if_arg_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("nodeHealth can't be null");
+
+    underTest.writeMine(null);
+  }
+
+  @Test
+  public void write_put_arg_into_map_sq_health_state_under_current_client_uuid() {
+    NodeHealth nodeHealth = randomNodeHealth();
+    Map<String, TimestampedNodeHealth> map = new HashMap<>();
+    doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
+    long clusterTime = random.nextLong();
+    String uuid = randomAlphanumeric(5);
+    when(hazelcastMember.getUuid()).thenReturn(uuid);
+    when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
+
+    underTest.writeMine(nodeHealth);
+
+    assertThat(map.size()).isEqualTo(1);
+    assertThat(map.get(uuid)).isEqualTo(new TimestampedNodeHealth(nodeHealth, clusterTime));
+    assertThat(logging.getLogs()).isEmpty();
+  }
+
+  @Test
+  public void write_logs_map_sq_health_state_content_and_NodeHealth_to_be_added_if_TRACE() {
+    logging.setLevel(Level.TRACE);
+    NodeHealth newNodeHealth = randomNodeHealth();
+    Map<String, TimestampedNodeHealth> map = new HashMap<>();
+    map.put(randomAlphanumeric(4), new TimestampedNodeHealth(randomNodeHealth(), random.nextLong()));
+    doReturn(new HashMap<>(map)).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
+    String uuid = randomAlphanumeric(5);
+    when(hazelcastMember.getUuid()).thenReturn(uuid);
+
+    underTest.writeMine(newNodeHealth);
+
+    assertThat(logging.getLogs()).hasSize(1);
+    assertThat(logging.hasLog(Level.TRACE, "Reading " + map + " and adding " + newNodeHealth)).isTrue();
+  }
+
+  @Test
+  public void readAll_returns_all_NodeHealth_in_map_sq_health_state_for_existing_client_uuids_aged_less_than_30_seconds() {
+    NodeHealth[] nodeHealths = IntStream.range(0, 1 + random.nextInt(6)).mapToObj(i -> randomNodeHealth()).toArray(NodeHealth[]::new);
+    Map<String, TimestampedNodeHealth> allNodeHealths = new HashMap<>();
+    Map<String, NodeHealth> expected = new HashMap<>();
+    String randomUuidBase = randomAlphanumeric(5);
+    for (int i = 0; i < nodeHealths.length; i++) {
+      String memberUuid = randomUuidBase + i;
+      TimestampedNodeHealth timestampedNodeHealth = new TimestampedNodeHealth(nodeHealths[i], clusterTime - random.nextInt(30 * 1000));
+      allNodeHealths.put(memberUuid, timestampedNodeHealth);
+      if (random.nextBoolean()) {
+        expected.put(memberUuid, nodeHealths[i]);
+      }
+    }
+    doReturn(allNodeHealths).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
+    when(hazelcastMember.getMemberUuids()).thenReturn(expected.keySet());
+    when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
+
+    assertThat(underTest.readAll())
+      .containsOnly(expected.values().stream().toArray(NodeHealth[]::new));
+    assertThat(logging.getLogs()).isEmpty();
+  }
+
+  @Test
+  public void readAll_ignores_NodeHealth_of_30_seconds_before_cluster_time() {
+    NodeHealth nodeHealth = randomNodeHealth();
+    Map<String, TimestampedNodeHealth> map = new HashMap<>();
+    String memberUuid = randomAlphanumeric(5);
+    TimestampedNodeHealth timestampedNodeHealth = new TimestampedNodeHealth(nodeHealth, clusterTime - 30 * 1000);
+    map.put(memberUuid, timestampedNodeHealth);
+    doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
+    when(hazelcastMember.getMemberUuids()).thenReturn(map.keySet());
+    when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
+
+    assertThat(underTest.readAll()).isEmpty();
+  }
+
+  @Test
+  public void readAll_ignores_NodeHealth_of_more_than_30_seconds_before_cluster_time() {
+    NodeHealth nodeHealth = randomNodeHealth();
+    Map<String, TimestampedNodeHealth> map = new HashMap<>();
+    String memberUuid = randomAlphanumeric(5);
+    TimestampedNodeHealth timestampedNodeHealth = new TimestampedNodeHealth(nodeHealth, clusterTime - 30 * 1000 - random.nextInt(99));
+    map.put(memberUuid, timestampedNodeHealth);
+    doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
+    when(hazelcastMember.getMemberUuids()).thenReturn(map.keySet());
+    when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
+
+    assertThat(underTest.readAll()).isEmpty();
+  }
+
+  @Test
+  public void readAll_logs_map_sq_health_state_content_and_the_content_effectively_returned_if_TRACE() {
+    logging.setLevel(Level.TRACE);
+    Map<String, TimestampedNodeHealth> map = new HashMap<>();
+    String uuid = randomAlphanumeric(44);
+    NodeHealth nodeHealth = randomNodeHealth();
+    map.put(uuid, new TimestampedNodeHealth(nodeHealth, clusterTime - 1));
+    when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
+    when(hazelcastMember.getMemberUuids()).thenReturn(singleton(uuid));
+    doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
+
+    underTest.readAll();
+
+    assertThat(logging.getLogs()).hasSize(1);
+    assertThat(logging.hasLog(Level.TRACE, "Reading " + new HashMap<>(map) + " and keeping " + singleton(nodeHealth))).isTrue();
+  }
+
+  @Test
+  public void readAll_logs_message_for_each_non_existing_member_ignored_if_TRACE() {
+    logging.setLevel(Level.TRACE);
+    Map<String, TimestampedNodeHealth> map = new HashMap<>();
+    String memberUuid1 = randomAlphanumeric(44);
+    String memberUuid2 = randomAlphanumeric(44);
+    map.put(memberUuid1, new TimestampedNodeHealth(randomNodeHealth(), clusterTime - 1));
+    map.put(memberUuid2, new TimestampedNodeHealth(randomNodeHealth(), clusterTime - 1));
+    when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
+    doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
+
+    underTest.readAll();
+
+    assertThat(logging.getLogs()).hasSize(3);
+    assertThat(logging.getLogs(Level.TRACE))
+      .containsOnly(
+        "Reading " + new HashMap<>(map) + " and keeping []",
+        "Ignoring NodeHealth of member " + memberUuid1 + " because it is not part of the cluster at the moment",
+        "Ignoring NodeHealth of member " + memberUuid2 + " because it is not part of the cluster at the moment");
+  }
+
+  @Test
+  public void readAll_logs_message_for_each_timed_out_NodeHealth_ignored_if_TRACE() {
+    logging.setLevel(Level.TRACE);
+    Map<String, TimestampedNodeHealth> map = new HashMap<>();
+    String memberUuid1 = randomAlphanumeric(44);
+    String memberUuid2 = randomAlphanumeric(44);
+    map.put(memberUuid1, new TimestampedNodeHealth(randomNodeHealth(), clusterTime - 30 * 1000));
+    map.put(memberUuid2, new TimestampedNodeHealth(randomNodeHealth(), clusterTime - 30 * 1000));
+    doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
+    when(hazelcastMember.getMemberUuids()).thenReturn(ImmutableSet.of(memberUuid1, memberUuid2));
+    when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
+
+    underTest.readAll();
+
+    assertThat(logging.getLogs()).hasSize(3);
+    assertThat(logging.getLogs(Level.TRACE))
+      .containsOnly(
+        "Reading " + new HashMap<>(map) + " and keeping []",
+        "Ignoring NodeHealth of member " + memberUuid1 + " because it is too old",
+        "Ignoring NodeHealth of member " + memberUuid2 + " because it is too old");
+  }
+
+  @Test
+  public void clearMine_clears_entry_into_map_sq_health_state_under_current_client_uuid() {
+    Map<String, TimestampedNodeHealth> map = mock(Map.class);
+    doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
+    String uuid = randomAlphanumeric(5);
+    when(hazelcastMember.getUuid()).thenReturn(uuid);
+
+    underTest.clearMine();
+
+    verify(map).remove(uuid);
+    verifyNoMoreInteractions(map);
+    assertThat(logging.getLogs()).isEmpty();
+  }
+
+  @Test
+  public void clearMine_logs_map_sq_health_state_and_current_client_uuid_if_TRACE() {
+    logging.setLevel(Level.TRACE);
+    Map<String, TimestampedNodeHealth> map = new HashMap<>();
+    map.put(randomAlphanumeric(4), new TimestampedNodeHealth(randomNodeHealth(), random.nextLong()));
+    doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
+    String uuid = randomAlphanumeric(5);
+    when(hazelcastMember.getUuid()).thenReturn(uuid);
+
+    underTest.clearMine();
+
+    assertThat(logging.getLogs()).hasSize(1);
+    assertThat(logging.hasLog(Level.TRACE, "Reading " + map + " and clearing for " + uuid)).isTrue();
+  }
+
+  private NodeHealth randomNodeHealth() {
+    return newNodeHealthBuilder()
+      .setStatus(NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)])
+      .setDetails(newNodeDetailsBuilder()
+        .setType(random.nextBoolean() ? NodeDetails.Type.SEARCH : NodeDetails.Type.APPLICATION)
+        .setName(randomAlphanumeric(30))
+        .setHost(randomAlphanumeric(10))
+        .setPort(1 + random.nextInt(666))
+        .setStartedAt(1 + random.nextInt(852))
+        .build())
+      .build();
+  }
+}
diff --git a/server/sonar-process/src/test/resources/org/sonar/process/logback-test.xml b/server/sonar-process/src/test/resources/org/sonar/process/logback-test.xml
new file mode 100644 (file)
index 0000000..7252100
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<configuration debug="true">
+
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%d{HH:mm:ss.SSS} %-5level - %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <appender name="TESTING" class="org.sonar.process.TestLogbackAppender">
+    <encoder>
+      <pattern>%-5level %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="INFO">
+    <appender-ref ref="STDOUD" />
+    <appender-ref ref="TESTING" />
+  </root>
+</configuration>