1   /*
2   	StatCvs - CVS statistics generation 
3   	Copyright (C) 2002  Lukasz Pekacki <lukasz@pekacki.de>
4   	http://statcvs.sf.net/
5       
6   	This library 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 2.1 of the License, or (at your option) any later version.
10  
11  	This library 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
17  	License along with this library; if not, write to the Free Software
18  	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20  package net.sf.statsvn.input;
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.io.Reader;
26  import java.util.Calendar;
27  import java.util.Date;
28  import java.util.NoSuchElementException;
29  import java.util.TimeZone;
30  
31  import junit.framework.TestCase;
32  import net.sf.statcvs.input.LogSyntaxException;
33  import net.sf.statcvs.util.LookaheadReader;
34  
35  /**
36   * Tests for {@link SvnLogfileParser} and {@link FileBlockParser}. Most
37   * tests run the parser class on a logfile loaded from the file system and
38   * use a {@link MockBuilder} to verify the results.
39   * 
40   * @author Richard Cyganiak <richard@cyganiak.de>
41   * @version $Id: ParserTest.java,v 1.5 2004/10/10 11:29:07 cyganiak Exp $
42   */
43  public class ParserTest extends TestCase {
44  	private final static Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
45  
46  	private MockLogBuilder mock;
47  
48  	private RevisionData rev1;
49  
50  	public ParserTest(final String arg0) {
51  		super(arg0);
52  	}
53  
54  	/*
55  	 * @see TestCase#setUp()
56  	 */
57  	protected void setUp() throws Exception {
58  		super.setUp();
59  		mock = new MockLogBuilder();
60  		rev1 = new RevisionData();
61  	}
62  
63  	/**
64  	 * Tests simple.log
65  	 * @throws Exception on error
66  	 */
67  	public void testSimpleLog() throws Exception {
68  		mock.expectBuildModule("statcvs");
69  		mock.expectBuildFile("LICENSE", false, false);
70  		rev1.setRevisionNumber("1.1");
71  		rev1.setDate(createDate(2003, 06, 04, 19, 32, 58));
72  		rev1.setLoginName("cyganiak");
73  		rev1.setStateExp(true);
74  		rev1.setComment("renamed license.txt to LICENSE");
75  		mock.expectBuildRevision(rev1);
76  		parseLog("simple.log");
77  		mock.verify();
78  	}
79  
80  	/**
81  	 * Tests a logfile which was created when uncommited files were present in
82  	 * the working copy ("? filename" lines at the beginning). They must be
83  	 * ignored.
84  	 * @throws Exception on error
85  	 */
86  	public void testUncommittedFiles() throws Exception {
87  		mock.expectBuildModule("statcvs");
88  		mock.expectBuildFile("LICENSE", false, false);
89  		mock.expectCurrentRevisionNumber("1.1");
90  		parseLog("uncommitted-files.log");
91  		mock.verify();
92  	}
93  
94  	/**
95  	 * Tests two-files.log
96  	 * @throws Exception
97  	 */
98  	public void testTwoFiles() throws Exception {
99  		mock.expectBuildModule("statcvs");
100 		mock.expectBuildFile("LICENSE", false, false);
101 		mock.expectCurrentRevisionNumber("1.2");
102 		mock.expectNextRevision();
103 		mock.expectCurrentRevisionNumber("1.1");
104 		mock.expectBuildFile("README", false, false);
105 		mock.expectCurrentRevisionNumber("1.1");
106 		parseLog("two-files.log");
107 		mock.verify();
108 	}
109 
110 	/**
111 	 * Tests parsing a log with a file that has no selected revisions.
112 	 * Necessary when specifying ranges of dates or tags. The log still
113 	 * contains all files but some may have no selected revisions.
114 	 * @throws Exception
115 	 */
116 	public void testNoRevisionsSelected() throws Exception {
117 		mock.expectBuildModule("statcvs");
118 		mock.expectBuildFile("LICENSE", false, false);
119 		mock.expectBuildFile("README", false, false);
120 		mock.expectCurrentRevisionNumber("1.1");
121 		parseLog("no-revs-selected.log");
122 		mock.verify();
123 	}
124 
125 	/**
126 	 * Same as {@link #testNoRevisionsSelected}, but now the file has a description.
127 	 * The description must be ignored by the parser.
128 	 * @throws Exception
129 	 */
130 	public void testNoRevisionsSelectedWithDescription() throws Exception {
131 		mock.expectBuildModule("statcvs");
132 		mock.expectBuildFile("LICENSE", false, false);
133 		mock.expectBuildFile("README", false, false);
134 		mock.expectCurrentRevisionNumber("1.1");
135 		parseLog("no-revs-selected-w-description.log");
136 		mock.verify();
137 	}
138 
139 	/**
140 	 * Not sure why that was put in, but apparently we want the parser to
141 	 * deal gracefully with newlines at the end of the file.
142 	 * @throws Exception on error
143 	 */
144 	public void testEmptyLinesAfterEnd() throws Exception {
145 		mock.expectBuildModule("statcvs");
146 		mock.expectBuildFile("LICENSE", false, false);
147 		mock.expectNextRevision();
148 		parseLog("newlines-after-end.log");
149 		mock.verify();
150 	}
151 
152 	/**
153 	 * Tests if the parser can handle a revision delimiter in the comment.
154 	 * @throws Exception
155 	 */
156 	public void testRevisionDelimiterInComment() throws Exception {
157 		mock.expectBuildModule("statcvs");
158 		mock.expectBuildFile("LICENSE", false, false);
159 		mock.expectCurrentRevisionNumber("1.1");
160 		mock.expectCurrentComment("comment\n----------------------------\ncomment");
161 		mock.expectBuildFile("README", false, false);
162 		mock.expectCurrentRevisionNumber("1.1");
163 		parseLog("delimiter-in-comment.log");
164 		mock.verify();
165 	}
166 
167 	/**
168 	 * Tests for exception on empty logfile 
169 	 * @throws Exception
170 	 */
171 	public void testEmptyLog() throws Exception {
172 		final ByteArrayInputStream stream = new ByteArrayInputStream("".getBytes());
173 		// test needs to be changed to use the new mandatory repositoryFileManager
174 		final SvnLogfileParser parser = new SvnLogfileParser(null, stream, mock);
175 		try {
176 			parser.parse();
177 			fail("should have thrown LogSyntaxException");
178 		} catch (final LogSyntaxException expected) {
179 			// expected
180 		}
181 	}
182 
183 	/**
184 	 * Tests for exception on bogus logfile 
185 	 * @throws Exception
186 	 */
187 	public void testBogusLog() throws Exception {
188 		final ByteArrayInputStream stream = new ByteArrayInputStream("foo\nbar".getBytes());
189 		// test needs to be changed to use the new mandatory repositoryFileManager
190 		final SvnLogfileParser parser = new SvnLogfileParser(null, stream, mock);
191 		try {
192 			parser.parse();
193 			fail("should have thrown LogSyntaxException");
194 		} catch (final LogSyntaxException expected) {
195 			// expected
196 		}
197 	}
198 
199 	/**
200 	 * Tests the FileBlockParser for a first file
201 	 * @throws Exception
202 	 */
203 	public void testFirstFile() throws Exception {
204 		mock.expectBuildModule("statcvs");
205 		mock.expectBuildFile("LICENSE", false, false);
206 		mock.expectCurrentRevisionNumber("1.1");
207 		final Reader reader = new InputStreamReader(getClass().getResourceAsStream("simple.log2"));
208 		final LookaheadReader lookahead = new LookaheadReader(reader);
209 		lookahead.nextLine();
210 		//		new FileBlockParser(lookahead, mock, true).parse();
211 		mock.verify();
212 		reader.close();
213 	}
214 
215 	/**
216 	 * Tests the FileBlockParser for a non-first file
217 	 * @throws Exception
218 	 */
219 	public void testNonFirstFile() throws Exception {
220 		mock.expectBuildFile("LICENSE", false, false);
221 		mock.expectCurrentRevisionNumber("1.1");
222 		parseOneFile("simple.log2");
223 		mock.verify();
224 	}
225 
226 	/**
227 	 * Tests the FileBlockParser for a file with description
228 	 * @throws Exception
229 	 */
230 	public void testDescription() throws Exception {
231 		mock.expectBuildFile("LICENSE", false, false);
232 		mock.expectCurrentRevisionNumber("1.1");
233 		parseOneFile("description.log2");
234 		mock.verify();
235 	}
236 
237 	/**
238 	 * Tests parsing a log with a lock ("cyganiak: 1.1")
239 	 * @throws Exception on error
240 	 */
241 	public void testLocks() throws Exception {
242 		mock.expectBuildFile("LICENSE", false, false);
243 		mock.expectCurrentRevisionNumber("1.1");
244 		parseOneFile("locks.log2");
245 		mock.verify();
246 	}
247 
248 	/**
249 	 * Tests the FileBlockParser for a file with access list
250 	 * @throws Exception on error
251 	 */
252 	public void testAccessList() throws Exception {
253 		mock.expectBuildFile("LICENSE", false, false);
254 		mock.expectCurrentRevisionNumber("1.1");
255 		parseOneFile("access-list.log2");
256 		mock.verify();
257 	}
258 
259 	/**
260 	 * Test log with missing "symbolic names:" section. Such
261 	 * logs are created by the -N switch of the cvs log command. 
262 	 * @throws Exception on error
263 	 */
264 	public void testNoSymbolicNames() throws Exception {
265 		mock.expectBuildFile("LICENSE", false, false);
266 		mock.expectCurrentRevisionNumber("1.1");
267 		parseOneFile("no-symbolic-names.log2");
268 		mock.verify();
269 	}
270 
271 	/**
272 	 * Tests if attic files are correctly identified. 
273 	 * @throws Exception on error
274 	 * @see net.sf.statsvn.util.SvnLogUtilsTest.testIsInAttic
275 	 */
276 	public void testIsInAttic() throws Exception {
277 		mock.expectBuildFile("LICENSE", false, true);
278 		mock.expectCurrentRevisionNumber("1.1");
279 		parseOneFile("in-attic.log2");
280 		mock.verify();
281 	}
282 
283 	public void testTwoRevisions() throws Exception {
284 		mock.expectBuildFile("LICENSE", false, false);
285 		mock.expectCurrentRevisionNumber("1.2");
286 		mock.expectCurrentAuthor("jentzsch");
287 		mock.expectCurrentDate(createDate(2003, 6, 5, 19, 32, 58));
288 		mock.expectCurrentComment("comment2");
289 		mock.expectCurrentStateExp();
290 		mock.expectCurrentLines(10, 0);
291 		mock.expectNextRevision();
292 		mock.expectCurrentRevisionNumber("1.1");
293 		mock.expectCurrentAuthor("cyganiak");
294 		mock.expectCurrentDate(createDate(2003, 6, 4, 19, 32, 58));
295 		mock.expectCurrentComment("comment1");
296 		mock.expectCurrentNoLines();
297 		parseOneFile("two-revisions.log2");
298 		mock.verify();
299 	}
300 
301 	/**
302 	 * Tests the FileBlockParser for a binary file
303 	 * @throws Exception
304 	 */
305 	public void testBinary() throws Exception {
306 		mock.expectBuildFile("LICENSE", true, false);
307 		mock.expectCurrentRevisionNumber("1.1");
308 		parseOneFile("binary.log2");
309 		mock.verify();
310 	}
311 
312 	/**
313 	 * Recent CVS versions have a different date format
314 	 */
315 	public void testNewCVSDates() throws Exception {
316 		this.mock.expectBuildFile("LICENSE", false, false);
317 		this.mock.expectCurrentRevisionNumber("1.1");
318 		this.mock.expectCurrentDate(createDate(2004, 07, 18, 17, 42, 25));
319 		parseOneFile("newdate.log2");
320 		this.mock.verify();
321 	}
322 
323 	public void testPrematurelyEndingLog() throws Exception {
324 		this.mock.expectBuildFile("LICENSE", false, false);
325 		try {
326 			parseOneFile("premature-end.log2");
327 			fail();
328 		} catch (final NoSuchElementException ex) {
329 			// is expected because log ends right within a revision
330 		}
331 	}
332 
333 	private void parseLog(final String name) throws Exception {
334 		final InputStream stream = getClass().getResourceAsStream(name);
335 		// test needs to be changed to use the new mandatory repositoryFileManager
336 		new SvnLogfileParser(null, stream, mock).parse();
337 	}
338 
339 	private void parseOneFile(final String name) throws Exception {
340 		final Reader reader = new InputStreamReader(getClass().getResourceAsStream(name));
341 		final LookaheadReader lookahead = new LookaheadReader(reader);
342 		lookahead.nextLine();
343 		//		new FileBlockParser(lookahead, mock, false).parse();
344 		reader.close();
345 	}
346 
347 	private Date createDate(final int year, final int month, final int day, final int hour, final int minute, final int second) {
348 		calendar.set(year, month - 1, day, hour, minute, second);
349 		return calendar.getTime();
350 	}
351 }