View Javadoc
1   /*
2   Copyright (c) 2015 James Ahlborn
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess;
18  
19  import java.io.DataInputStream;
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.io.PrintWriter;
27  import java.lang.reflect.Field;
28  import java.nio.ByteBuffer;
29  import java.nio.channels.FileChannel;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Calendar;
33  import java.util.Date;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.TimeZone;
37  
38  import static com.healthmarketscience.jackcess.Database.*;
39  import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
40  import com.healthmarketscience.jackcess.impl.ByteUtil;
41  import com.healthmarketscience.jackcess.impl.DatabaseImpl;
42  import com.healthmarketscience.jackcess.impl.IndexData;
43  import com.healthmarketscience.jackcess.impl.IndexImpl;
44  import com.healthmarketscience.jackcess.impl.JetFormatTest;
45  import com.healthmarketscience.jackcess.impl.JetFormatTest.TestDB;
46  import com.healthmarketscience.jackcess.impl.RowIdImpl;
47  import com.healthmarketscience.jackcess.impl.RowImpl;
48  import com.healthmarketscience.jackcess.util.MemFileChannel;
49  import org.junit.Assert;
50  
51  /**
52   * Utilty code for the test cases.
53   *
54   * @author James Ahlborn
55   */
56  public class TestUtil 
57  {
58    public static final TimeZone TEST_TZ =
59      TimeZone.getTimeZone("America/New_York");
60    
61    private static final ThreadLocal<Boolean> _autoSync =         
62      new ThreadLocal<Boolean>();
63  
64    private TestUtil() {}
65  
66    static void setTestAutoSync(boolean autoSync) {
67      _autoSync.set(autoSync);
68    }
69  
70    static void clearTestAutoSync() {
71      _autoSync.remove();
72    }
73  
74    static boolean getTestAutoSync() {
75      Boolean autoSync = _autoSync.get();
76      return ((autoSync != null) ? autoSync : Database.DEFAULT_AUTO_SYNC);
77    }
78  
79    public static Database open(FileFormat fileFormat, File file) 
80      throws Exception 
81    {
82      return open(fileFormat, file, false);
83    }
84  
85    public static Database open(FileFormat fileFormat, File file, boolean inMem) 
86      throws Exception 
87    {
88      FileChannel channel = (inMem ? MemFileChannel.newChannel(
89                                 file, DatabaseImpl.RW_CHANNEL_MODE) 
90                             : null);
91      final Database db = new DatabaseBuilder(file).setReadOnly(true)
92        .setAutoSync(getTestAutoSync()).setChannel(channel).open();
93      Assert.assertEquals("Wrong JetFormat.", 
94                   DatabaseImpl.getFileFormatDetails(fileFormat).getFormat(), 
95                   ((DatabaseImpl)db).getFormat());
96      Assert.assertEquals("Wrong FileFormat.", fileFormat, db.getFileFormat());
97      return db;
98    }
99  
100   public static Database open(TestDB testDB) throws Exception {
101     return open(testDB.getExpectedFileFormat(), testDB.getFile());
102   }
103 
104   public static Database openMem(TestDB testDB) throws Exception {
105     return open(testDB.getExpectedFileFormat(), testDB.getFile(), true);
106   }
107 
108   public static Database create(FileFormat fileFormat) throws Exception {
109     return create(fileFormat, false);
110   }
111 
112   public static Database create(FileFormat fileFormat, boolean keep) 
113     throws Exception 
114   {
115     return create(fileFormat, keep, false);
116   }
117 
118   public static Database createMem(FileFormat fileFormat) throws Exception {
119     return create(fileFormat, false, true);
120   }
121 
122   private static Database create(FileFormat fileFormat, boolean keep, 
123                                  boolean inMem) 
124     throws Exception 
125   {
126     FileChannel channel = (inMem ? MemFileChannel.newChannel() : null);
127 
128     if (fileFormat == FileFormat.GENERIC_JET4) {
129       // while we don't support creating GENERIC_JET4 as a jackcess feature,
130       // we do want to be able to test these types of dbs
131       InputStream inStream = null;
132       OutputStream outStream = null;
133       try {
134         inStream = TestUtil.class.getClassLoader()
135           .getResourceAsStream("emptyJet4.mdb");
136         File f = createTempFile(keep);
137         if (channel != null) {
138           JetFormatTest.transferDbFrom(channel, inStream);
139         } else {
140           ByteUtil.copy(inStream, outStream = new FileOutputStream(f));
141           outStream.close();
142         }
143         return new DatabaseBuilder(f)
144           .setAutoSync(getTestAutoSync()).setChannel(channel).open();
145       } finally {
146         ByteUtil.closeQuietly(inStream);
147         ByteUtil.closeQuietly(outStream);
148       }
149     }
150      
151     return new DatabaseBuilder(createTempFile(keep)).setFileFormat(fileFormat)
152         .setAutoSync(getTestAutoSync()).setChannel(channel).create();
153   }
154 
155 
156   public static Database openCopy(TestDB testDB) throws Exception {
157     return openCopy(testDB, false);
158   }
159 
160   public static Database openCopy(TestDB testDB, boolean keep)
161     throws Exception
162   {
163     return openCopy(testDB.getExpectedFileFormat(), testDB.getFile(), keep);
164   }
165 
166   public static Database openCopy(FileFormat fileFormat, File file)
167     throws Exception
168   {
169     return openCopy(fileFormat, file, false);
170   }
171 
172   public static Database openCopy(FileFormat fileFormat, File file,
173                                   boolean keep)
174     throws Exception
175   {
176     File tmp = createTempFile(keep);
177     copyFile(file, tmp);
178     Database db = new DatabaseBuilder(tmp).setAutoSync(getTestAutoSync()).open();
179     Assert.assertEquals("Wrong JetFormat.", 
180                  DatabaseImpl.getFileFormatDetails(fileFormat).getFormat(),
181                  ((DatabaseImpl)db).getFormat());
182     Assert.assertEquals("Wrong FileFormat.", fileFormat, db.getFileFormat());
183     return db;
184   }
185 
186 
187   static Object[] createTestRow(String col1Val) {
188     return new Object[] {col1Val, "R", "McCune", 1234, (byte) 0xad, 555.66d,
189         777.88f, (short) 999, new Date()};
190   }
191 
192   public static Object[] createTestRow() {
193     return createTestRow("Tim");
194   }
195   
196   static Map<String,Object> createTestRowMap(String col1Val) {
197     return createExpectedRow("A", col1Val, "B", "R", "C", "McCune",
198                              "D", 1234, "E", (byte) 0xad, "F", 555.66d,
199                              "G", 777.88f, "H", (short) 999, "I", new Date());
200   }
201 
202   public static void createTestTable(Database db) throws Exception {
203     new TableBuilder("test")
204       .addColumn(new ColumnBuilder("A", DataType.TEXT))
205       .addColumn(new ColumnBuilder("B", DataType.TEXT))
206       .addColumn(new ColumnBuilder("C", DataType.TEXT))
207       .addColumn(new ColumnBuilder("D", DataType.LONG))
208       .addColumn(new ColumnBuilder("E", DataType.BYTE))
209       .addColumn(new ColumnBuilder("F", DataType.DOUBLE))
210       .addColumn(new ColumnBuilder("G", DataType.FLOAT))
211       .addColumn(new ColumnBuilder("H", DataType.INT))
212       .addColumn(new ColumnBuilder("I", DataType.SHORT_DATE_TIME))
213       .toTable(db);
214   }
215 
216   public static String createString(int len) {
217     return createString(len, 'a');
218   }
219 
220   static String createNonAsciiString(int len) {
221     return createString(len, '\u0CC0');
222   }
223     
224   private static String createString(int len, char firstChar) {
225     StringBuilder builder = new StringBuilder(len);
226     for(int i = 0; i < len; ++i) {
227       builder.append((char)(firstChar + (i % 26)));
228     }
229     return builder.toString();
230   }
231 
232   static void assertRowCount(int expectedRowCount, Table table)
233     throws Exception
234   {
235     Assert.assertEquals(expectedRowCount, countRows(table));
236     Assert.assertEquals(expectedRowCount, table.getRowCount());
237   }
238   
239   public static int countRows(Table table) throws Exception {
240     int rtn = 0;
241     for(Map<String, Object> row : CursorBuilder.createCursor(table)) {
242       rtn++;
243     }
244     return rtn;
245   }
246 
247   public static void assertTable(
248       List<? extends Map<String, Object>> expectedTable, 
249       Table table)
250     throws IOException
251   {
252     assertCursor(expectedTable, CursorBuilder.createCursor(table));
253   }
254   
255   public static void assertCursor(
256       List<? extends Map<String, Object>> expectedTable, 
257       Cursor cursor)
258   {
259     List<Map<String, Object>> foundTable =
260       new ArrayList<Map<String, Object>>();
261     for(Map<String, Object> row : cursor) {
262       foundTable.add(row);
263     }
264     Assert.assertEquals(expectedTable.size(), foundTable.size());
265     for(int i = 0; i < expectedTable.size(); ++i) {
266       Assert.assertEquals(expectedTable.get(i), foundTable.get(i));
267     } 
268   }
269   
270   public static RowImpl createExpectedRow(Object... rowElements) {
271     RowImpl row = new RowImpl((RowIdImpl)null);
272     for(int i = 0; i < rowElements.length; i += 2) {
273       row.put((String)rowElements[i],
274               rowElements[i + 1]);
275     }
276     return row;
277   }    
278 
279   public static List<Row> createExpectedTable(Row... rows) {
280     return Arrays.<Row>asList(rows);
281   }    
282   
283   public static void dumpDatabase(Database mdb) throws Exception {
284     dumpDatabase(mdb, false);
285   }
286 
287   public static void dumpDatabase(Database mdb, boolean systemTables)
288     throws Exception
289   {
290     dumpDatabase(mdb, systemTables, new PrintWriter(System.out, true));
291   }
292 
293   public static void dumpTable(Table table) throws Exception {
294     dumpTable(table, new PrintWriter(System.out, true));
295   }
296 
297   static void dumpDatabase(Database mdb, boolean systemTables,
298                            PrintWriter writer) throws Exception
299   {
300     writer.println("DATABASE:");
301     for(Table table : mdb) {
302       dumpTable(table, writer);
303     }
304     if(systemTables) {
305       for(String sysTableName : mdb.getSystemTableNames()) {
306         dumpTable(mdb.getSystemTable(sysTableName), writer);
307       }
308     }
309   }
310 
311   static void dumpTable(Table table, PrintWriter writer) throws Exception {
312     // make sure all indexes are read
313     for(Index index : table.getIndexes()) {
314       ((IndexImpl)index).initialize();
315     }
316     
317     writer.println("TABLE: " + table.getName());
318     List<String> colNames = new ArrayList<String>();
319     for(Column col : table.getColumns()) {
320       colNames.add(col.getName());
321     }
322     writer.println("COLUMNS: " + colNames);
323     for(Map<String, Object> row : CursorBuilder.createCursor(table)) {
324       writer.println(massageRow(row));
325     }
326   }
327 
328   private static Map<String,Object> massageRow(Map<String, Object> row)
329     throws IOException
330   {
331       for(Map.Entry<String, Object> entry : row.entrySet()) {
332         Object v = entry.getValue();
333         if(v instanceof byte[]) {
334           // make byte[] printable
335           byte[] bv = (byte[])v;
336           entry.setValue(ByteUtil.toHexString(ByteBuffer.wrap(bv), bv.length));
337         } else if(v instanceof ComplexValueForeignKey) {
338           // deref complex values
339           String str = "ComplexValue(" + v + ")" +
340             ((ComplexValueForeignKey)v).getValues();
341           entry.setValue(str);
342         }
343       }
344 
345       return row;
346   }
347 
348   static void dumpIndex(Index index) throws Exception {
349     dumpIndex(index, new PrintWriter(System.out, true));
350   }
351 
352   static void dumpIndex(Index index, PrintWriter writer) throws Exception {
353     writer.println("INDEX: " + index);
354     IndexData.EntryCursor ec = ((IndexImpl)index).cursor();
355     IndexData.Entry lastE = ec.getLastEntry();
356     IndexData.Entry e = null;
357     while((e = ec.getNextEntry()) != lastE) {
358       writer.println(e);
359     }
360   }
361 
362   static void assertSameDate(Date expected, Date found)
363   {
364     if(expected == found) {
365       return;
366     }
367     if((expected == null) || (found == null)) {
368       throw new AssertionError("Expected " + expected + ", found " + found);
369     }
370     long expTime = expected.getTime();
371     long foundTime = found.getTime();
372     // there are some rounding issues due to dates being stored as doubles,
373     // but it results in a 1 millisecond difference, so i'm not going to worry
374     // about it
375     if((expTime != foundTime) && (Math.abs(expTime - foundTime) > 1)) {
376       throw new AssertionError("Expected " + expTime + " (" + expected +
377                                "), found " + foundTime + " (" + found + ")");
378     }
379   }
380   
381   static void copyFile(File srcFile, File dstFile)
382     throws IOException
383   {
384     // FIXME should really be using commons io FileUtils here, but don't want
385     // to add dep for one simple test method
386     byte[] buf = new byte[1024];
387     OutputStream ostream = new FileOutputStream(dstFile);
388     InputStream istream = new FileInputStream(srcFile);
389     try {
390       int numBytes = 0;
391       while((numBytes = istream.read(buf)) >= 0) {
392         ostream.write(buf, 0, numBytes);
393       }
394     } finally {
395       ostream.close();
396     }
397   }
398 
399   static File createTempFile(boolean keep) throws Exception {
400     File tmp = File.createTempFile("databaseTest", ".mdb");
401     if(keep) {
402       System.out.println("Created " + tmp);
403     } else {
404       tmp.deleteOnExit();
405     }
406     return tmp;
407   }
408 
409   public static void clearTableCache(Database db) throws Exception
410   {
411     Field f = db.getClass().getDeclaredField("_tableCache");
412     f.setAccessible(true);
413     Object val = f.get(db);
414     f = val.getClass().getDeclaredField("_tables");
415     f.setAccessible(true);
416     val = f.get(val);
417     ((Map<?,?>)val).clear();
418   }
419   
420   public static byte[] toByteArray(File file)
421     throws IOException
422   {
423     return toByteArray(new FileInputStream(file), file.length());
424   }
425 
426   public static byte[] toByteArray(InputStream in, long length)
427     throws IOException
428   {
429     // FIXME should really be using commons io IOUtils here, but don't want
430     // to add dep for one simple test method
431     try {
432       DataInputStream din = new DataInputStream(in);
433       byte[] bytes = new byte[(int)length];
434       din.readFully(bytes);
435       return bytes;
436     } finally {
437       in.close();
438     }
439   }
440 
441   static void checkTestDBTable1RowABCDEFG(final TestDB testDB, final Table table, final Row row)
442           throws IOException {
443     Assert.assertEquals("testDB: " + testDB + "; table: " + table, "abcdefg", row.get("A"));
444     Assert.assertEquals("hijklmnop", row.get("B"));
445     Assert.assertEquals(new Byte((byte) 2), row.get("C"));
446     Assert.assertEquals(new Short((short) 222), row.get("D"));
447     Assert.assertEquals(new Integer(333333333), row.get("E"));
448     Assert.assertEquals(new Double(444.555d), row.get("F"));
449     final Calendar cal = Calendar.getInstance();
450     cal.setTime(row.getDate("G"));
451     Assert.assertEquals(Calendar.SEPTEMBER, cal.get(Calendar.MONTH));
452     Assert.assertEquals(21, cal.get(Calendar.DAY_OF_MONTH));
453     Assert.assertEquals(1974, cal.get(Calendar.YEAR));
454     Assert.assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
455     Assert.assertEquals(0, cal.get(Calendar.MINUTE));
456     Assert.assertEquals(0, cal.get(Calendar.SECOND));
457     Assert.assertEquals(0, cal.get(Calendar.MILLISECOND));
458     Assert.assertEquals(Boolean.TRUE, row.get("I"));
459   }
460 
461   static void checkTestDBTable1RowA(final TestDB testDB, final Table table, final Row row)
462           throws IOException {
463     Assert.assertEquals("testDB: " + testDB + "; table: " + table, "a", row.get("A"));
464     Assert.assertEquals("b", row.get("B"));
465     Assert.assertEquals(new Byte((byte) 0), row.get("C"));
466     Assert.assertEquals(new Short((short) 0), row.get("D"));
467     Assert.assertEquals(new Integer(0), row.get("E"));
468     Assert.assertEquals(new Double(0d), row.get("F"));
469     final Calendar cal = Calendar.getInstance();
470     cal.setTime(row.getDate("G"));
471     Assert.assertEquals(Calendar.DECEMBER, cal.get(Calendar.MONTH));
472     Assert.assertEquals(12, cal.get(Calendar.DAY_OF_MONTH));
473     Assert.assertEquals(1981, cal.get(Calendar.YEAR));
474     Assert.assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
475     Assert.assertEquals(0, cal.get(Calendar.MINUTE));
476     Assert.assertEquals(0, cal.get(Calendar.SECOND));
477     Assert.assertEquals(0, cal.get(Calendar.MILLISECOND));
478     Assert.assertEquals(Boolean.FALSE, row.get("I"));
479   }
480 
481 }