View Javadoc

1   /*
2    * #%L
3    * JavaHg
4    * %%
5    * Copyright (C) 2011 aragost Trifork ag
6    * %%
7    * Permission is hereby granted, free of charge, to any person obtaining a copy
8    * of this software and associated documentation files (the "Software"), to deal
9    * in the Software without restriction, including without limitation the rights
10   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11   * copies of the Software, and to permit persons to whom the Software is
12   * furnished to do so, subject to the following conditions:
13   * 
14   * The above copyright notice and this permission notice shall be included in
15   * all copies or substantial portions of the Software.
16   * 
17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23   * THE SOFTWARE.
24   * #L%
25   */
26  package com.aragost.javahg.test;
27  
28  import java.io.File;
29  import java.io.IOException;
30  import java.util.List;
31  import java.util.Random;
32  
33  import org.junit.Assert;
34  import org.junit.Test;
35  
36  import com.aragost.javahg.BaseRepository;
37  import com.aragost.javahg.Changeset;
38  import com.aragost.javahg.Repository;
39  import com.aragost.javahg.RepositoryConfiguration;
40  import com.aragost.javahg.commands.AddCommand;
41  import com.aragost.javahg.commands.CommitCommand;
42  import com.aragost.javahg.commands.ExecutionException;
43  import com.aragost.javahg.commands.LogCommand;
44  import com.aragost.javahg.commands.RemoveCommand;
45  import com.google.common.collect.Lists;
46  import com.google.common.io.Files;
47  
48  /**
49   * Stress test the system by running many threads sending commands to
50   * a repository.
51   */
52  public class StressTest extends AbstractTestCase {
53  
54      private static final int CYCLES_PER_THREAD = 25;
55      private static final int NUMBER_OF_THREADS = 20;
56      private static volatile Random RANDOM = new Random(0);
57  
58      @Test
59      public void stressTest() throws InterruptedException {
60          BaseRepository repo = getTestRepository();
61          Stats stats = new Stats();
62          List<StressThread> threads = Lists.newArrayList();
63          for (int i = 0; i < NUMBER_OF_THREADS; i++) {
64              StressThread t = new StressThread(CYCLES_PER_THREAD, i, stats, repo);
65              t.start();
66              threads.add(t);
67          }
68  
69          for (StressThread cycleThread : threads) {
70              cycleThread.join();
71          }
72          Assert.assertEquals(2 * CYCLES_PER_THREAD * NUMBER_OF_THREADS, stats.changes + stats.noChanges);
73          System.err.println("Cache stats: " + repo.getCacheStats());
74      }
75  
76      @Test
77      public void stressTestConcurrency() throws InterruptedException, IOException {
78          RepositoryConfiguration conf = makeRepoConf();
79          conf.setConcurrency(3);
80          BaseRepository repo = Repository.create(conf, Files.createTempDir());
81  
82          Stats stats = new Stats();
83          List<StressThread> threads = Lists.newArrayList();
84  
85          // Write some initial changesets
86          writeFile(repo, "a", "123");
87          AddCommand.on(repo).execute();
88          CommitCommand.on(repo).message("a").user("setup").execute();
89          writeFile(repo, "b", "123");
90          AddCommand.on(repo).execute();
91          CommitCommand.on(repo).message("b").user("setup").execute();
92          writeFile(repo, "c", "123");
93          AddCommand.on(repo).execute();
94          CommitCommand.on(repo).message("c").user("setup").execute();
95  
96          for (int i = 0; i < NUMBER_OF_THREADS; i++) {
97              StressThread t = new StressThread2(CYCLES_PER_THREAD * 10, i, stats, repo);
98              t.start();
99              threads.add(t);
100         }
101 
102         for (StressThread cycleThread : threads) {
103             cycleThread.join();
104         }
105         Assert.assertEquals(20 * CYCLES_PER_THREAD * NUMBER_OF_THREADS, stats.changes + stats.noChanges);
106         System.err.println("Cache stats: " + repo.getCacheStats());
107     }
108     
109     private String randomString(int length) {
110         StringBuilder builder = new StringBuilder(length);
111         for (int i = 0; i < length; i++) {
112             builder.append((char) ('a' + RANDOM.nextInt(26)));
113         }
114         return builder.toString();
115     }
116 
117     class StressThread extends Thread {
118 
119         private int count;
120         protected final int threadId;
121         protected final Stats stats;
122         protected final BaseRepository repo;
123 
124         public StressThread(int count, int threadId, Stats stats, BaseRepository repo) {
125             this.count = count;
126             this.threadId = threadId;
127             this.stats = stats;
128             this.repo = repo;
129         }
130 
131         @Override
132         public void run() {
133             for (int i = 0; i < this.count; i++) {
134                 try {
135                     performCycle();
136                 } catch (IOException e) {
137                     throw new RuntimeException(e);
138                 }
139             }
140         }
141 
142         protected void performCycle() throws IOException {
143             String newFile = randomString(1);
144             String fileContent = randomString(20);
145             writeFile(repo, newFile, fileContent);
146             AddCommand.on(repo).execute();
147             CommitCommand commitCmd = CommitCommand.on(repo).user("thread: " + this.threadId).message(
148                     "msg: " + this.threadId);
149             Changeset x = commitCmd.execute();
150             if (x != null) {
151                 Changeset y = repo.changeset(x.getNode());
152                 Assert.assertSame(x, y);
153                 stats.incChanges();
154             } else {
155                 // Change committed by other thread
156                 stats.incNoChanges();
157             }
158 
159             File file = new File(repo.getDirectory(), randomString(1));
160             try {
161                 RemoveCommand.on(repo).execute(file.getPath());
162             } catch (ExecutionException e) {
163                 // The file did not exist. Maybe it never existed, or
164                 // maybe it was deleted by another Mercurial process.
165                 // Note that even if we check for existence before
166                 // calling 'hg remove', then another thread might
167                 // delete it before Mercurial and thus cause an error.
168             }
169 
170             commitCmd.execute();
171             if (commitCmd.getReturnCode() == 1) {
172                 // Change committed by other thread
173                 stats.incNoChanges();
174             } else {
175                 stats.incChanges();
176             }
177 
178         }
179     }
180 
181     class StressThread2 extends StressThread {
182 
183         public StressThread2(int count, int threadId, Stats stats,
184                 BaseRepository repo) {
185             super(count, threadId, stats, repo);
186         }
187         
188         @Override
189         protected void performCycle() throws IOException {
190             if (threadId == 0) {
191                 super.performCycle();
192             } else {
193                 LogCommand.on(repo).limit(3);
194                 stats.incChanges();
195                 stats.incChanges();
196             }
197         }
198     }
199 
200     private static class Stats {
201         private int noChanges = 0;
202         private int changes = 0;
203 
204         synchronized void incNoChanges() {
205             this.noChanges++;
206         }
207 
208         synchronized void incChanges() {
209             this.changes++;
210         }
211     }
212 }