From b4380a006722c7a5245c04cd213852ee067691b1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 11 Jun 2012 16:24:14 +0200 Subject: [PATCH] added unit tests for git log command --- .../spi/AbstractGitCommandTestBase.java | 185 +++++++++++++++++ .../scm/repository/spi/GitLogCommandTest.java | 188 ++++++++++++++++++ .../scm/repository/spi/scm-git-spi-test.zip | Bin 0 -> 19256 bytes 3 files changed, 373 insertions(+) create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java new file mode 100644 index 0000000000..0878e570c7 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository.spi; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.io.Closeables; +import com.google.common.io.Resources; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryTestData; +import sonia.scm.util.IOUtil; + +import static org.junit.Assert.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import java.net.URL; + +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * + * @author Sebastian Sdorra + */ +public class AbstractGitCommandTestBase +{ + + /** + * Method description + * + */ + @Before + public void before() + { + repositoryDirectory = createRepositoryDirectory(); + } + + /** + * Method description + * + * + * @return + */ + protected Repository createRepository() + { + return RepositoryTestData.createHeartOfGold(); + } + + /** + * Method description + * + * + * @return + * + * @throws IOException + */ + protected File createRepositoryDirectory() + { + File folder = null; + + try + { + folder = tempFolder.newFolder(); + folder.mkdirs(); + extract(folder); + } + catch (IOException ex) + { + fail(ex.getMessage()); + } + + return folder; + } + + /** + * Method description + * + * + * @param folder + * + * @throws IOException + */ + private void extract(File folder) throws IOException + { + URL url = + Resources.getResource("sonia/scm/repository/spi/scm-git-spi-test.zip"); + ZipInputStream zip = null; + + try + { + zip = new ZipInputStream(url.openStream()); + + ZipEntry entry = zip.getNextEntry(); + + while (entry != null) + { + File file = new File(folder, entry.getName()); + File parent = file.getParentFile(); + + if (!parent.exists()) + { + parent.mkdirs(); + } + + if (entry.isDirectory()) + { + file.mkdirs(); + } + else + { + OutputStream output = null; + + try + { + output = new FileOutputStream(file); + IOUtil.copy(zip, output); + } + finally + { + Closeables.closeQuietly(output); + } + } + + zip.closeEntry(); + entry = zip.getNextEntry(); + } + } + finally + { + Closeables.closeQuietly(zip); + } + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + /** Field description */ + protected Repository repository = createRepository(); + + /** Field description */ + protected File repositoryDirectory; +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java new file mode 100644 index 0000000000..9365cd1696 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -0,0 +1,188 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository.spi; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.junit.Test; + +import sonia.scm.repository.Changeset; +import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.Modifications; + +import static org.hamcrest.Matchers.*; + +import static org.junit.Assert.*; + +/** + * + * @author Sebastian Sdorra + */ +public class GitLogCommandTest extends AbstractGitCommandTestBase +{ + + /** + * Method description + * + */ + @Test + public void testGetAll() + { + ChangesetPagingResult result = new GitLogCommand( + repository, + repositoryDirectory).getChangesets( + new LogCommandRequest()); + + assertNotNull(result); + assertEquals(4, result.getTotal()); + assertEquals(4, result.getChangesets().size()); + } + + /** + * Method description + * + */ + @Test + public void testGetAllByPath() + { + LogCommandRequest request = new LogCommandRequest(); + + request.setPath("a.txt"); + + ChangesetPagingResult result = + new GitLogCommand(repository, repositoryDirectory).getChangesets(request); + + assertNotNull(result); + assertEquals(2, result.getTotal()); + assertEquals(2, result.getChangesets().size()); + assertEquals("3f76a12f08a6ba0dc988", result.getChangesets().get(0).getId()); + assertEquals("435df2f061add3589cb3", result.getChangesets().get(1).getId()); + } + + /** + * Method description + * + */ + @Test + public void testGetAllWithLimit() + { + LogCommandRequest request = new LogCommandRequest(); + + request.setPagingLimit(2); + + ChangesetPagingResult result = + new GitLogCommand(repository, repositoryDirectory).getChangesets(request); + + assertNotNull(result); + assertEquals(4, result.getTotal()); + assertEquals(2, result.getChangesets().size()); + + Changeset c1 = result.getChangesets().get(0); + + assertNotNull(c1); + assertEquals("86a6645eceefe8b9a247", c1.getId()); + + Changeset c2 = result.getChangesets().get(1); + + assertNotNull(c2); + assertEquals("3f76a12f08a6ba0dc988", c2.getId()); + } + + /** + * Method description + * + */ + @Test + public void testGetCommit() + { + GitLogCommand command = new GitLogCommand(repository, repositoryDirectory); + Changeset c = command.getChangeset("435df2f061add3589cb3"); + + assertNotNull(c); + assertEquals("435df2f061add3589cb3", c.getId()); + assertEquals("added a and b files", c.getDescription()); + checkDate(c.getDate()); + assertEquals("Douglas Adams", c.getAuthor().getName()); + assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail()); + assertEquals("added a and b files", c.getDescription()); + + Modifications mods = c.getModifications(); + + assertNotNull(mods); + assertTrue("modified list should be empty", mods.getModified().isEmpty()); + assertTrue("removed list should be empty", mods.getRemoved().isEmpty()); + assertFalse("added list should not be empty", mods.getAdded().isEmpty()); + assertEquals(2, mods.getAdded().size()); + assertThat(mods.getAdded(), contains("a.txt", "b.txt")); + } + + /** + * Method description + * + */ + @Test + public void testGetRange() + { + LogCommandRequest request = new LogCommandRequest(); + + request.setStartChangeset("592d797cd36432e59141"); + request.setEndChangeset("435df2f061add3589cb3"); + + ChangesetPagingResult result = + new GitLogCommand(repository, repositoryDirectory).getChangesets(request); + + assertNotNull(result); + assertEquals(2, result.getTotal()); + assertEquals(2, result.getChangesets().size()); + + Changeset c1 = result.getChangesets().get(0); + Changeset c2 = result.getChangesets().get(1); + + assertNotNull(c1); + assertEquals("592d797cd36432e59141", c1.getId()); + assertNotNull(c2); + assertEquals("435df2f061add3589cb3", c2.getId()); + } + + /** + * Method description + * + * + * @param date + */ + private void checkDate(Long date) + { + assertNotNull(date); + assertTrue("Date should not be older than current date", + date < System.currentTimeMillis()); + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip new file mode 100644 index 0000000000000000000000000000000000000000..88bedbea9c1268f70b52a76225a26b8ebaa3b9c8 GIT binary patch literal 19256 zcmcJ02Rzl^|G!;X*)t(K>n_(tL}f%qk(qI)y;oL|k(9^^WseBSOp+NwC@Nc`(y)>! zl;8W3xaA|?|Kst$Jx&jLbf2&D+OP9^oui{cN=8Y9|CBqmDHiFDXVL&a;}Tf|NZw>%cpS8IE6{Irhtad9n)Rx#n0 zC!M+m!Yu|wABb9S9kA{iBO$`?VNKu`fWSqqU2m}h^zn-f*n^s~(y>+IuESeO#s!O$ zv~`vI&C`{?1x_d!i-l{3lz?I% z0gLnZ_mNIcc3w_MH?)W3afzzF z->KH`(x`&u@IE-BPqAy?zFF_)zw6KtC@97LZQqtq?*|->y zgGwwp5jx_p-~$mo?LH<}gJy%{0)_?8x%L+=NtZ5rd*{S``e-7b$4w~S58qnp768%u zlj8Nht{$|by}h;DPDLAK?$`}tYD~GI?W`u+rLUPW8(OL!c%YXm+Hm36hc|-Km&zV> z*j@a5%h&S`*KKCO&kV{v3qL#LoA=i|ragr%zuNSS?K4 z+JjL^huLeDm?Fvdm=~&Dqi-jFsd|%3gxji^9dKq+)gVuyI9<5^GQD0%vNGwjn3utX^w=Y%jc5g-iC4+T|_jI zqqCL3eJdq8v#JP=I-9Xx)xtNVUCVDmBQZ;zY<>xa%hIAzM!jDwVU&4y%&Y6)t@vE2 zUous$Nq z?YG-^CP>v~`F4llC(Y|FF*C%3yzLV(k@*4eb(J6lIk{lPH^R^k*z=13Yd3)Cf-RRt z_glXp?+T6GvlfE1ymPg~{o?%6L_$}t>KYdnRXbk!L5;)H8J)P@(gMpKnxwPq5L680kzx@FxQ;MsmDA%E6>MoP1Wwt9g)1 zKy>%w0Ms)RgAiYAiue)Vm(({VU2^J1LSf0+M7(~;H=|P120fFdW7RH|R7_&_z1C$J z_TnVg+Ucc!i)N71<@WBo#i;ZuST!4>TDde|%3fI!EPatCh(2E7{;GISHIH}xf~n1b zG(*esl^F2zH?Mk)W|e~W|Io9z*i_wv)2xqtFKa1xOWDGv&!-62MJa_M51^pGxwkw+ zXPTGg(pZK7Je{mjC-C?&kibRl$#2RZzIAqGy55W+3lr-OJHXd`ql&Qu`t~~_X798? zP1n$gSsZRLJFRi(gK|i@pd5p3TKL|3VE(ZtR}Z%n+~KswFuvQIQN|)1e0vQ%c_+P-aO0N^h{ua2 zh2r{&rIq-HQW?4VTV-6`eAu>k}nJOsU zN!&J*;wyCUeGUt)b!0lxk<_`sGl35LD&&`Zvs+s70#3Yd7uOsW7Mcsz(#{|{R}!$d zAopTiemX;J3c|y!T-80`dxXwi*4|89v#xhuLqde|f%7v<{pjZ|>hV18F9QM`++rfK zUfBoQdsoJ%cqM)v7RcM>sUT3O8Cx1(;WrsPCQo!$T9ICAUkHV8M(xGY<7rV3%{b$= zXrG2n?5+!-FyiMO_Y%0xLF~2PGuEVgVrJjUTSJ}@&j;ztoJkYREAny¥30zs@-M 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