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