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