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.BufferedReader;
29  import java.io.File;
30  import java.io.FileInputStream;
31  import java.io.IOException;
32  import java.io.InputStreamReader;
33  import java.nio.charset.Charset;
34  import java.util.Collection;
35  import java.util.logging.Level;
36  import java.util.logging.Logger;
37  
38  import org.junit.After;
39  import org.junit.Assert;
40  import org.junit.Assume;
41  import org.junit.runner.RunWith;
42  
43  import com.aragost.javahg.BaseRepository;
44  import com.aragost.javahg.Changeset;
45  import com.aragost.javahg.HgVersion;
46  import com.aragost.javahg.Repository;
47  import com.aragost.javahg.RepositoryConfiguration;
48  import com.aragost.javahg.RepositoryConfiguration.CachePolicy;
49  import com.aragost.javahg.commands.AddCommand;
50  import com.aragost.javahg.commands.CommitCommand;
51  import com.aragost.javahg.commands.UpdateCommand;
52  import com.aragost.javahg.commands.VersionCommand;
53  import com.aragost.javahg.internals.AbstractCommand;
54  import com.aragost.javahg.internals.GenericCommand;
55  import com.aragost.javahg.internals.HgInputStream;
56  import com.aragost.javahg.internals.RuntimeIOException;
57  import com.aragost.javahg.internals.Server;
58  import com.aragost.javahg.internals.Utils;
59  import com.google.common.collect.ObjectArrays;
60  import com.google.common.io.Files;
61  
62  /**
63   * Base class for test cases.
64   */
65  @RunWith(org.junit.runners.BlockJUnit4ClassRunner.class)
66  public abstract class AbstractTestCase {
67  
68      // The jul root logger is changed in the initialization of this
69      // class. A strong reference must be maintained to the logger. See
70      // LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE on
71      // http://findbugs.sourceforge.net/bugDescriptions.html
72      private static final Logger JUL_ROOT_LOGGER = Logger.getLogger("");
73  
74      private static int count = 0;
75  
76      private BaseRepository testRepository;
77  
78      private BaseRepository testRepository2;
79  
80      protected static final RepositoryConfiguration REPO_CONF;
81  
82      static {
83          REPO_CONF = makeRepoConf();
84  
85          JUL_ROOT_LOGGER.setLevel(Level.WARNING);
86  
87          File dir = Files.createTempDir();
88          BaseRepository repo = Repository.create(REPO_CONF, dir);
89          HgVersion version = VersionCommand.on(repo).execute();
90          repo.close();
91          try {
92              deleteTempDir(dir);
93          } catch (IOException e) {
94              System.err.println("JavaHg: Failed to remove temp dir: " + dir.getAbsolutePath());
95          }
96  
97          System.err.println("JavaHg test using Mercurial version: " + version + ", binary: " + REPO_CONF.getHgBin());
98      }
99  
100     public File createMercurialRepository() {
101         File dir = Files.createTempDir();
102         Server server = new Server(RepositoryConfiguration.DEFAULT.getHgBin(),
103                                    RepositoryConfiguration.DEFAULT.getEncoding());
104         server.initMecurialRepository(dir);
105         return dir;
106     }
107 
108     protected static RepositoryConfiguration makeRepoConf() {
109         RepositoryConfiguration conf = new RepositoryConfiguration();
110         conf.setCachePolicy(CachePolicy.WEAK);
111         conf.addExtension(JavaHgTestExtension.class);
112 
113         return conf;
114     }
115 
116     public Charset utf8() {
117         return Charset.forName("UTF-8");
118     }
119 
120     public BaseRepository getTestRepository() {
121         if (this.testRepository == null) {
122             File dir = Files.createTempDir();
123             this.testRepository = Repository.create(REPO_CONF, dir);
124         }
125         return this.testRepository;
126     }
127 
128     public BaseRepository getTestRepository2() {
129         if (this.testRepository2 == null) {
130             File dir = Files.createTempDir();
131             this.testRepository2 = Repository.create(REPO_CONF, dir);
132         }
133         return this.testRepository2;
134     }
135 
136     /**
137      * Write to a file in the test repository
138      * 
139      * @param name
140      * @param content
141      * @throws IOException
142      */
143     public void writeFile(String name, String content) throws IOException {
144         writeFile(getTestRepository(), name, content);
145     }
146 
147     public void writeFile(BaseRepository repo, String name, String content) throws IOException {
148         File file = new File(repo.getDirectory(), name);
149         Files.write(content, file, utf8());
150     }
151     
152     /**
153      * Write something to the file in the test repository.
154      * <p>
155      * Each call to this method will write different content
156      * 
157      * @param name
158      * @throws IOException
159      */
160     public void writeFile(String name) throws IOException {
161         writeFile(name, String.valueOf(count++) + "\n");
162     }
163 
164     public void appendFile(String name) throws IOException {
165         File file = new File(getTestRepository().getDirectory(), name);
166         Files.append(String.valueOf(count++) + "\n", file, utf8());
167     }
168 
169     /**
170      * Read first line of the file
171      * 
172      * @param name
173      * @return
174      * @throws IOException
175      */
176     public String readFile(String name) throws IOException {
177         File file = new File(getTestRepository().getDirectory(), name);
178         return Files.readFirstLine(file, utf8());
179     }
180 
181     /**
182      * Delete the specified file from the working copy of the test
183      * repository.
184      * 
185      * @param name
186      */
187     public void deleteFile(String name) {
188         File file = new File(getTestRepository().getDirectory(), name);
189         boolean deleted = file.delete();
190         if (!deleted) {
191             throw new RuntimeException("Could not delete: " + file);
192         }
193     }
194 
195     /**
196      * Commit the changes in the test repository
197      * 
198      * @throws IOException
199      */
200     public Changeset commit() throws IOException {
201         Repository repo = getTestRepository();
202         AddCommand.on(repo).execute();
203         CommitCommand cmd = CommitCommand.on(repo).user("testcase").message("testcase: " + getClass().getName());
204         return cmd.execute();
205     }
206 
207     /**
208      * Create a new changeset in the test repository.
209      * 
210      * @return the changeset Created
211      * @throws IOException
212      */
213     public Changeset createChangeset() throws IOException {
214         writeFile("dummyFileForCreatingChangesets", String.valueOf(count++));
215         return commit();
216     }
217 
218     /**
219      * Update the test repository to the specified changeset
220      * 
221      * @param cs
222      * @throws IOException
223      */
224     public void update(Changeset cs) throws IOException {
225         UpdateCommand.on(getTestRepository()).clean().rev(cs.getNode()).execute();
226     }
227 
228     @After
229     public void closeTestRepository() throws IOException {
230         if (this.testRepository != null) {
231             this.testRepository.close();
232             deleteTempDir(this.testRepository.getDirectory());
233             this.testRepository = null;
234         }
235         if (this.testRepository2 != null) {
236             this.testRepository2.close();
237             deleteTempDir(this.testRepository2.getDirectory());
238             this.testRepository2 = null;
239         }
240     }
241 
242     /**
243      * Return an absolute File object referencing a file in the
244      * specified repository.
245      * 
246      * @param repo
247      * @param parts
248      * @return
249      */
250     public static File repoFile(Repository repo, String... parts) {
251         File result = repo.getDirectory();
252         for (String part : parts) {
253             result = new File(result, part);
254         }
255         return result;
256     }
257 
258     /**
259      * The error text for missing files is different on Windows
260      * compared to Linux/Mac
261      * 
262      * @return
263      */
264     public static String getMissingFileErrorText() {
265         String error = "No such file or directory";
266         String osName = System.getProperty("os.name");
267         if (osName.startsWith("Windows")) {
268             error = "The system cannot find the file specified";
269         }
270         return error;
271     }
272 
273     /**
274      * Create a temp directory, and return the canonical file object
275      * (i.e. no symlinks).
276      * 
277      * @return
278      * @throws IOException
279      */
280     protected static File createTempDir() throws IOException {
281         return Files.createTempDir().getCanonicalFile();
282     }
283 
284     /**
285      * Delete a directory in the system temporary directory
286      * (java.io.tmpdir).
287      * 
288      * @throws IOException
289      */
290     public static void deleteTempDir(File file) throws IOException {
291         Utils.deleteTempDir(file);
292     }
293 
294     protected void assertSingleton(Object obj, Collection<?> coll) {
295         Assert.assertEquals(obj, Utils.single(coll));
296     }
297 
298     protected void assertFailedExecution(AbstractCommand cmd) {
299         assertFailedExecution(cmd, "");
300     }
301 
302     protected void assertFailedExecution(AbstractCommand cmd, String msg) {
303         if (msg.length() > 0) {
304             msg = " Message: " + msg;
305         }
306         Assert.fail("Exception expected! Return code: " + cmd.getReturnCode() + "." + msg);
307     }
308 
309     /**
310      * Execute a Mercurial command direcotry for a repository, with
311      * out using the server
312      * 
313      * @param repo
314      * @param hgrcPath
315      * @param args
316      */
317     protected void execHgCommand(BaseRepository repo, String... args) {
318         try {
319             String[] arr = Utils.arrayConcat(new String[] { REPO_CONF.getHgBin(), "--repo",
320                     repo.getDirectory().getAbsolutePath() }, args);
321 
322             Process process = Runtime.getRuntime().exec(arr);
323             String stderr = Utils.readStream(process.getErrorStream(), repo.newDecoder());
324             Utils.consumeAll(process.getInputStream());
325 
326             if (process.waitFor() != 0) {
327                 throw new RuntimeException(stderr);
328             }
329         } catch (IOException e) {
330             throw new RuntimeIOException(e);
331         } catch (InterruptedException e) {
332             throw Utils.asRuntime(e);
333         }
334     }
335 
336     /**
337      * Warning: invoking commands on this server may put it in an invalid state.
338      * Use AbstractCommand.launchStream(String...) instead.
339      * 
340      * @param repo The repo
341      * @return A Server from the repo
342      */
343     protected Server getFirstServer(Repository repo) {
344         return repo.getServerPool().getServers().get(0);
345     }
346 
347     protected static ServeState startServing(Repository repo,
348             String... additionalConfig) {
349 
350         // On windows hg serve --port 0 doesn't print the port it's listening on
351         Assume.assumeTrue(!Utils.isWindows());
352 
353         final File pidFile;
354 
355         try {
356             pidFile = File.createTempFile("javahg", ".pid");
357 
358             final Process process = Runtime.getRuntime().exec(
359                     ObjectArrays.concat(
360                             new String[] {
361                                     RepositoryConfiguration.DEFAULT.getHgBin(),
362                                     "serve", "--port", "0", "-d", "--pid-file",
363                                     pidFile.toString(), "-R",
364                                     repo.getDirectory().toString() },
365                             additionalConfig, String.class));
366 
367             HgInputStream in = new HgInputStream(process.getInputStream(),
368                     repo.newDecoder());
369 
370             Assert.assertTrue(in.find("(bound to *:".getBytes()));
371             final int port = in.readDecimal().intValue();
372             in.close();
373 
374             return new ServeState() {
375 
376                 public int getPort() {
377                     return port;
378                 }
379 
380                 public void stop() {
381                     BufferedReader in = null;;
382                     try {
383                         // probably already dead:
384                         process.destroy();
385 
386                         in = new BufferedReader(new InputStreamReader(new FileInputStream(pidFile)));
387                         killProcess(Integer.parseInt(in.readLine()));
388 
389                     } catch (Exception e) {
390                         throw Utils.asRuntime(e);
391                     } finally {
392                         try {
393                             in.close();
394                         } catch (IOException e) {
395                             throw Utils.asRuntime(e);
396                         }
397                     }
398                 }};
399         } catch (IOException e) {
400             throw Utils.asRuntime(e);
401         }
402     }
403     
404     /**
405      * Kill a process
406      * 
407      * @param pid The process id
408      */
409     protected static void killProcess(int pid) {
410         try {
411             Runtime rt = Runtime.getRuntime();
412             if (System.getProperty("os.name").toLowerCase().indexOf("windows") > -1) {
413                 rt.exec("taskkill " + pid).waitFor();
414             } else {
415                 rt.exec(new String[] { "kill", "-9", "" + pid }).waitFor();
416             }
417         } catch (IOException e) {
418             throw Utils.asRuntime(e);
419         } catch (InterruptedException e) {
420             throw Utils.asRuntime(e);
421         }
422     }
423 
424     protected static class TestableCommand extends GenericCommand {
425 
426         public TestableCommand(Repository repository, String commandName) {
427             super(repository, commandName);
428         }
429 
430         public HgInputStream executeToStream(String... args) {
431              return launchStream(args);
432         }
433     }
434 
435     protected interface ServeState
436     {
437         public int getPort();
438         public void stop();
439     }
440 }