]> source.dussan.org Git - sonarqube.git/blob
1d3aae9e24772b5adccaec85f10f9ed506071cd2
[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;
21
22 import com.google.common.base.Throwables;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.ExecutorService;
25 import java.util.concurrent.Executors;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.atomic.AtomicInteger;
28 import org.junit.After;
29 import org.junit.Test;
30 import org.sonar.server.platform.Platform;
31 import org.sonar.server.platform.db.migration.engine.MigrationEngine;
32
33 import static org.assertj.core.api.Assertions.assertThat;
34 import static org.mockito.Mockito.mock;
35
36 public class DatabaseMigrationImplConcurrentAccessTest {
37
38   private ExecutorService pool = Executors.newFixedThreadPool(2);
39   /**
40    * Latch is used to make sure both testing threads try and call {@link DatabaseMigrationImpl#startIt()} at the
41    * same time
42    */
43   private CountDownLatch latch = new CountDownLatch(2);
44
45   /**
46    * Implementation of execute runs Runnable synchronously
47    */
48   private DatabaseMigrationExecutorService executorService = new DatabaseMigrationExecutorServiceAdaptor() {
49     @Override
50     public void execute(Runnable command) {
51       command.run();
52     }
53   };
54   private AtomicInteger triggerCount = new AtomicInteger();
55   private MigrationEngine incrementingMigrationEngine = new MigrationEngine() {
56     @Override
57     public void execute() {
58       // need execute to consume some time to avoid UT to fail because it ran too fast and threads never executed concurrently
59       try {
60         Thread.sleep(200);
61       } catch (InterruptedException e) {
62         Throwables.propagate(e);
63       }
64       triggerCount.incrementAndGet();
65     }
66   };
67   private MutableDatabaseMigrationState migrationState = mock(MutableDatabaseMigrationState.class);
68   private Platform platform = mock(Platform.class);
69   private DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(executorService, migrationState, incrementingMigrationEngine, platform);
70
71   @After
72   public void tearDown() {
73     pool.shutdownNow();
74   }
75
76   @Test
77   public void two_concurrent_calls_to_startit_call_migration_engine_only_once() throws Exception {
78     pool.submit(new CallStartit());
79     pool.submit(new CallStartit());
80
81     pool.awaitTermination(2, TimeUnit.SECONDS);
82
83     assertThat(triggerCount.get()).isOne();
84   }
85
86   private class CallStartit implements Runnable {
87     @Override
88     public void run() {
89       latch.countDown();
90       try {
91         latch.await();
92       } catch (InterruptedException e) {
93         // propagate interruption
94         Thread.currentThread().interrupt();
95       }
96       underTest.startIt();
97     }
98   }
99 }