/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.types; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.Arrays; import java.util.Random; import org.apache.flink.core.memory.DataInputView; import org.apache.flink.core.memory.DataInputViewStreamWrapper; import org.apache.flink.core.memory.DataOutputView; import org.apache.flink.core.memory.DataOutputViewStreamWrapper; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class RecordTest { private static final long SEED = 354144423270432543L; private final Random rand = new Random(RecordTest.SEED); private DataInputView in; private DataOutputView out; // Couple of test values private final StringValue origVal1 = new StringValue("Hello World!"); private final DoubleValue origVal2 = new DoubleValue(Math.PI); private final IntValue origVal3 = new IntValue(1337); @Before public void setUp() throws Exception { PipedInputStream pipeIn = new PipedInputStream(1024*1024); PipedOutputStream pipeOut = new PipedOutputStream(pipeIn); this.in = new DataInputViewStreamWrapper(pipeIn); this.out = new DataOutputViewStreamWrapper(pipeOut); } @Test public void testEmptyRecordSerialization() { try { // test deserialize into self Record empty = new Record(); empty.write(this.out); empty.read(in); Assert.assertTrue("Deserialized Empty record is not another empty record.", empty.getNumFields() == 0); // test deserialize into new empty = new Record(); empty.write(this.out); empty = new Record(); empty.read(this.in); Assert.assertTrue("Deserialized Empty record is not another empty record.", empty.getNumFields() == 0); } catch (Throwable t) { Assert.fail("Test failed due to an exception: " + t.getMessage()); } } @Test public void testAddField() { try { // Add a value to an empty record Record record = new Record(); assertTrue(record.getNumFields() == 0); record.addField(this.origVal1); assertTrue(record.getNumFields() == 1); assertTrue(origVal1.getValue().equals(record.getField(0, StringValue.class).getValue())); // Add 100 random integers to the record record = new Record(); for (int i = 0; i < 100; i++) { IntValue orig = new IntValue(this.rand.nextInt()); record.addField(orig); IntValue rec = record.getField(i, IntValue.class); assertTrue(record.getNumFields() == i + 1); assertTrue(orig.getValue() == rec.getValue()); } // Add 3 values of different type to the record record = new Record(this.origVal1, this.origVal2); record.addField(this.origVal3); assertTrue(record.getNumFields() == 3); StringValue recVal1 = record.getField(0, StringValue.class); DoubleValue recVal2 = record.getField(1, DoubleValue.class); IntValue recVal3 = record.getField(2, IntValue.class); assertTrue("The value of the first field has changed", recVal1.equals(this.origVal1)); assertTrue("The value of the second field changed", recVal2.equals(this.origVal2)); assertTrue("The value of the third field has changed", recVal3.equals(this.origVal3)); } catch (Throwable t) { Assert.fail("Test failed due to an exception: " + t.getMessage()); } } // @Test // public void testInsertField() { // Record record = null; // int oldLen = 0; // // // Create filled record and insert in the middle // record = new Record(this.origVal1, this.origVal3); // record.insertField(1, this.origVal2); // // assertTrue(record.getNumFields() == 3); // // StringValue recVal1 = record.getField(0, StringValue.class); // DoubleValue recVal2 = record.getField(1, DoubleValue.class); // IntValue recVal3 = record.getField(2, IntValue.class); // // assertTrue(recVal1.getValue().equals(this.origVal1.getValue())); // assertTrue(recVal2.getValue() == this.origVal2.getValue()); // assertTrue(recVal3.getValue() == this.origVal3.getValue()); // // record = this.generateFilledDenseRecord(100); // // // Insert field at the first position of the record // oldLen = record.getNumFields(); // record.insertField(0, this.origVal1); // assertTrue(record.getNumFields() == oldLen + 1); // assertTrue(this.origVal1.equals(record.getField(0, StringValue.class))); // // // Insert field at the end of the record // oldLen = record.getNumFields(); // record.insertField(oldLen, this.origVal2); // assertTrue(record.getNumFields() == oldLen + 1); // assertTrue(this.origVal2 == record.getField(oldLen, DoubleValue.class)); // // // Insert several random fields into the record // for (int i = 0; i < 100; i++) { // int pos = rand.nextInt(record.getNumFields()); // IntValue val = new IntValue(rand.nextInt()); // record.insertField(pos, val); // assertTrue(val.getValue() == record.getField(pos, IntValue.class).getValue()); // } // } @Test public void testRemoveField() { Record record = null; int oldLen = 0; // Create filled record and remove field from the middle record = new Record(this.origVal1, this.origVal2); record.addField(this.origVal3); record.removeField(1); assertTrue(record.getNumFields() == 2); StringValue recVal1 = record.getField(0, StringValue.class); IntValue recVal2 = record.getField(1, IntValue.class); assertTrue(recVal1.getValue().equals(this.origVal1.getValue())); assertTrue(recVal2.getValue() == this.origVal3.getValue()); record = this.generateFilledDenseRecord(100); // Remove field from the first position of the record oldLen = record.getNumFields(); record.removeField(0); assertTrue(record.getNumFields() == oldLen - 1); // Remove field from the end of the record oldLen = record.getNumFields(); record.removeField(oldLen - 1); assertTrue(record.getNumFields() == oldLen - 1); // Insert several random fields into the record record = this.generateFilledDenseRecord(100); for (int i = 0; i < 100; i++) { oldLen = record.getNumFields(); int pos = this.rand.nextInt(record.getNumFields()); record.removeField(pos); assertTrue(record.getNumFields() == oldLen - 1); } } // @Test // public void testProjectLong() { // Record record = new Record(); // long mask = 0; // // record.addField(this.origVal1); // record.addField(this.origVal2); // record.addField(this.origVal3); // // // Keep all fields // mask = 7L; // record.project(mask); // assertTrue(record.getNumFields() == 3); // assertTrue(this.origVal1.getValue().equals(record.getField(0, StringValue.class).getValue())); // assertTrue(this.origVal2.getValue() == record.getField(1, DoubleValue.class).getValue()); // assertTrue(this.origVal3.getValue() == record.getField(2, IntValue.class).getValue()); // // // Keep the first and the last field // mask = 5L; // Keep the first and the third/ last column // record.project(mask); // assertTrue(record.getNumFields() == 2); // assertTrue(this.origVal1.getValue().equals(record.getField(0, StringValue.class).getValue())); // assertTrue(this.origVal3.getValue() == record.getField(1, IntValue.class).getValue()); // // // Keep no fields // mask = 0L; // record.project(mask); // assertTrue(record.getNumFields() == 0); // // // Keep random fields // record = this.generateFilledDenseRecord(64); // mask = this.generateRandomBitmask(64); // // record.project(mask); // assertTrue(record.getNumFields() == Long.bitCount(mask)); // } // @Test // public void testProjectLongArray() { // Record record = this.generateFilledDenseRecord(256); // long[] mask = {1L, 1L, 1L, 1L}; // // record.project(mask); // assertTrue(record.getNumFields() == 4); // // record = this.generateFilledDenseRecord(612); // mask = new long[10]; // int numBits = 0; // // for (int i = 0; i < mask.length; i++) { // int offset = i * Long.SIZE; // int numFields = ((offset + Long.SIZE) < record.getNumFields()) ? Long.SIZE : record.getNumFields() - offset; // mask[i] = this.generateRandomBitmask(numFields); // numBits += Long.bitCount(mask[i]); // } // // record.project(mask); // assertTrue(record.getNumFields() == numBits); // } @Test public void testSetNullInt() { try { Record record = this.generateFilledDenseRecord(58); record.setNull(42); assertTrue(record.getNumFields() == 58); assertTrue(record.getField(42, IntValue.class) == null); } catch (Throwable t) { Assert.fail("Test failed due to an exception: " + t.getMessage()); } } @Test public void testSetNullLong() { try { Record record = this.generateFilledDenseRecord(58); long mask = generateRandomBitmask(58); record.setNull(mask); for (int i = 0; i < 58; i++) { if (((1L << i) & mask) != 0) { assertTrue(record.getField(i, IntValue.class) == null); } } assertTrue(record.getNumFields() == 58); } catch (Throwable t) { Assert.fail("Test failed due to an exception: " + t.getMessage()); } } @Test public void testSetNullLongArray() { try { Record record = this.generateFilledDenseRecord(612); long[] mask = {1L, 1L, 1L, 1L}; record.setNull(mask); assertTrue(record.getField(0, IntValue.class) == null); assertTrue(record.getField(64, IntValue.class) == null); assertTrue(record.getField(128, IntValue.class) == null); assertTrue(record.getField(192, IntValue.class) == null); mask = new long[10]; for (int i = 0; i < mask.length; i++) { int offset = i * Long.SIZE; int numFields = ((offset + Long.SIZE) < record.getNumFields()) ? Long.SIZE : record.getNumFields() - offset; mask[i] = this.generateRandomBitmask(numFields); } record.setNull(mask); } catch (Throwable t) { Assert.fail("Test failed due to an exception: " + t.getMessage()); } } // @Test // public void testAppend() { // Record record1 = this.generateFilledDenseRecord(42); // Record record2 = this.generateFilledDenseRecord(1337); // // IntValue rec1val = record1.getField(12, IntValue.class); // IntValue rec2val = record2.getField(23, IntValue.class); // // record1.append(record2); // // assertTrue(record1.getNumFields() == 42 + 1337); // assertTrue(rec1val.getValue() == record1.getField(12, IntValue.class).getValue()); // assertTrue(rec2val.getValue() == record1.getField(42 + 23, IntValue.class).getValue()); // } // @Test // public void testUnion() { // } @Test public void testUpdateBinaryRepresentations() { try { // TODO: this is not an extensive test of updateBinaryRepresentation() // and should be extended! Record r = new Record(); IntValue i1 = new IntValue(1); IntValue i2 = new IntValue(2); try { r.setField(1, i1); r.setField(3, i2); r.setNumFields(5); r.updateBinaryRepresenation(); i1 = new IntValue(3); i2 = new IntValue(4); r.setField(7, i1); r.setField(8, i2); r.updateBinaryRepresenation(); assertTrue(r.getField(1, IntValue.class).getValue() == 1); assertTrue(r.getField(3, IntValue.class).getValue() == 2); assertTrue(r.getField(7, IntValue.class).getValue() == 3); assertTrue(r.getField(8, IntValue.class).getValue() == 4); } catch (RuntimeException re) { fail("Error updating binary representation: " + re.getMessage()); } // Tests an update where modified and unmodified fields are interleaved r = new Record(); for (int i = 0; i < 8; i++) { r.setField(i, new IntValue(i)); } try { // serialize and deserialize to remove all buffered info r.write(this.out); r = new Record(); r.read(this.in); r.setField(1, new IntValue(10)); r.setField(4, new StringValue("Some long value")); r.setField(5, new StringValue("An even longer value")); r.setField(10, new IntValue(10)); r.write(this.out); r = new Record(); r.read(this.in); assertTrue(r.getField(0, IntValue.class).getValue() == 0); assertTrue(r.getField(1, IntValue.class).getValue() == 10); assertTrue(r.getField(2, IntValue.class).getValue() == 2); assertTrue(r.getField(3, IntValue.class).getValue() == 3); assertTrue(r.getField(4, StringValue.class).getValue().equals("Some long value")); assertTrue(r.getField(5, StringValue.class).getValue().equals("An even longer value")); assertTrue(r.getField(6, IntValue.class).getValue() == 6); assertTrue(r.getField(7, IntValue.class).getValue() == 7); assertTrue(r.getField(8, IntValue.class) == null); assertTrue(r.getField(9, IntValue.class) == null); assertTrue(r.getField(10, IntValue.class).getValue() == 10); } catch (RuntimeException | IOException re) { fail("Error updating binary representation: " + re.getMessage()); } } catch (Throwable t) { Assert.fail("Test failed due to an exception: " + t.getMessage()); } } @Test public void testDeSerialization() { try { StringValue origValue1 = new StringValue("Hello World!"); IntValue origValue2 = new IntValue(1337); Record record1 = new Record(origValue1, origValue2); Record record2 = new Record(); try { // De/Serialize the record record1.write(this.out); record2.read(this.in); assertTrue(record1.getNumFields() == record2.getNumFields()); StringValue rec1Val1 = record1.getField(0, StringValue.class); IntValue rec1Val2 = record1.getField(1, IntValue.class); StringValue rec2Val1 = record2.getField(0, StringValue.class); IntValue rec2Val2 = record2.getField(1, IntValue.class); assertTrue(origValue1.equals(rec1Val1)); assertTrue(origValue2.equals(rec1Val2)); assertTrue(origValue1.equals(rec2Val1)); assertTrue(origValue2.equals(rec2Val2)); } catch (IOException e) { fail("Error writing Record"); e.printStackTrace(); } } catch (Throwable t) { Assert.fail("Test failed due to an exception: " + t.getMessage()); } } @Test public void testClear() throws IOException { try { Record record = new Record(new IntValue(42)); record.write(this.out); Assert.assertEquals(42, record.getField(0, IntValue.class).getValue()); record.setField(0, new IntValue(23)); record.write(this.out); Assert.assertEquals(23, record.getField(0, IntValue.class).getValue()); record.clear(); Assert.assertEquals(0, record.getNumFields()); Record record2 = new Record(new IntValue(42)); record2.read(in); Assert.assertEquals(42, record2.getField(0, IntValue.class).getValue()); record2.read(in); Assert.assertEquals(23, record2.getField(0, IntValue.class).getValue()); } catch (Throwable t) { Assert.fail("Test failed due to an exception: " + t.getMessage()); } } private Record generateFilledDenseRecord(int numFields) { Record record = new Record(); for (int i = 0; i < numFields; i++) { record.addField(new IntValue(this.rand.nextInt())); } return record; } private long generateRandomBitmask(int numFields) { long bitmask = 0L; long tmp = 0L; for (int i = 0; i < numFields; i++) { tmp = this.rand.nextBoolean() ? 1L : 0L; bitmask = bitmask | (tmp << i); } return bitmask; } @Test public void blackBoxTests() { try { final Value[][] values = new Value[][] { // empty {}, // exactly 8 fields {new IntValue(55), new StringValue("Hi there!"), new LongValue(457354357357135L), new IntValue(345), new IntValue(-468), new StringValue("This is the message and the message is this!"), new LongValue(0L), new IntValue(465)}, // exactly 16 fields {new IntValue(55), new StringValue("Hi there!"), new LongValue(457354357357135L), new IntValue(345), new IntValue(-468), new StringValue("This is the message and the message is this!"), new LongValue(0L), new IntValue(465), new IntValue(55), new StringValue("Hi there!"), new LongValue(457354357357135L), new IntValue(345), new IntValue(-468), new StringValue("This is the message and the message is this!"), new LongValue(0L), new IntValue(465)}, // exactly 8 nulls {null, null, null, null, null, null, null, null}, // exactly 16 nulls {null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null}, // arbitrary example {new IntValue(56), null, new IntValue(-7628761), new StringValue("A test string")}, // a very long field {new StringValue(createRandomString(this.rand, 15)), new StringValue(createRandomString(this.rand, 1015)), new StringValue(createRandomString(this.rand, 32))}, // two very long fields {new StringValue(createRandomString(this.rand, 1265)), null, new StringValue(createRandomString(this.rand, 855))} }; for (Value[] value : values) { blackboxTestRecordWithValues(value, this.rand, this.in, this.out); } // random test with records with a small number of fields for (int i = 0; i < 10000; i++) { final Value[] fields = createRandomValues(this.rand, 0, 32); blackboxTestRecordWithValues(fields, this.rand, this.in, this.out); } // random tests with records with a moderately large number of fields for (int i = 0; i < 1000; i++) { final Value[] fields = createRandomValues(this.rand, 20, 150); blackboxTestRecordWithValues(fields, this.rand, this.in, this.out); } } catch (Throwable t) { Assert.fail("Test failed due to an exception: " + t.getMessage()); } } static void blackboxTestRecordWithValues(Value[] values, Random rnd, DataInputView reader, DataOutputView writer) throws Exception { final int[] permutation1 = createPermutation(rnd, values.length); final int[] permutation2 = createPermutation(rnd, values.length); // test adding and retrieving without intermediate binary updating Record rec = new Record(); for (int i = 0; i < values.length; i++) { final int pos = permutation1[i]; rec.setField(pos, values[pos]); } testAllRetrievalMethods(rec, permutation2, values); // test adding and retrieving with full binary updating rec = new Record(); for (int i = 0; i < values.length; i++) { final int pos = permutation1[i]; rec.setField(pos, values[pos]); } rec.updateBinaryRepresenation(); testAllRetrievalMethods(rec, permutation2, values); // test adding and retrieving with intermediate binary updating rec = new Record(); int updatePos = rnd.nextInt(values.length + 1); for (int i = 0; i < values.length; i++) { if (i == updatePos) { rec.updateBinaryRepresenation(); } final int pos = permutation1[i]; rec.setField(pos, values[pos]); } if (updatePos == values.length) { rec.updateBinaryRepresenation(); } testAllRetrievalMethods(rec, permutation2, values); // test adding and retrieving with full stream serialization and deserialization into a new record rec = new Record(); for (int i = 0; i < values.length; i++) { final int pos = permutation1[i]; rec.setField(pos, values[pos]); } rec.write(writer); rec = new Record(); rec.read(reader); testAllRetrievalMethods(rec, permutation2, values); // test adding and retrieving with full stream serialization and deserialization into the same record rec = new Record(); for (int i = 0; i < values.length; i++) { final int pos = permutation1[i]; rec.setField(pos, values[pos]); } rec.write(writer); rec.read(reader); testAllRetrievalMethods(rec, permutation2, values); // test adding and retrieving with partial stream serialization and deserialization into a new record rec = new Record(); updatePos = rnd.nextInt(values.length + 1); for (int i = 0; i < values.length; i++) { if (i == updatePos) { rec.write(writer); rec = new Record(); rec.read(reader); } final int pos = permutation1[i]; rec.setField(pos, values[pos]); } if (updatePos == values.length) { rec.write(writer); rec = new Record(); rec.read(reader); } testAllRetrievalMethods(rec, permutation2, values); // test adding and retrieving with partial stream serialization and deserialization into the same record rec = new Record(); updatePos = rnd.nextInt(values.length + 1); for (int i = 0; i < values.length; i++) { if (i == updatePos) { rec.write(writer); rec.read(reader); } final int pos = permutation1[i]; rec.setField(pos, values[pos]); } if (updatePos == values.length) { rec.write(writer); rec.read(reader); } testAllRetrievalMethods(rec, permutation2, values); // test adding and retrieving with partial stream serialization and deserialization into a new record rec = new Record(); updatePos = rnd.nextInt(values.length + 1); for (int i = 0; i < values.length; i++) { if (i == updatePos) { rec.write(writer); rec = new Record(); rec.read(reader); } final int pos = permutation1[i]; rec.setField(pos, values[pos]); } rec.write(writer); rec = new Record(); rec.read(reader); testAllRetrievalMethods(rec, permutation2, values); // test adding and retrieving with partial stream serialization and deserialization into the same record rec = new Record(); updatePos = rnd.nextInt(values.length + 1); for (int i = 0; i < values.length; i++) { if (i == updatePos) { rec.write(writer); rec.read(reader); } final int pos = permutation1[i]; rec.setField(pos, values[pos]); } rec.write(writer); rec.read(reader); testAllRetrievalMethods(rec, permutation2, values); } public static void testAllRetrievalMethods(Record rec, int[] permutation, Value[] expected) throws Exception { // test getField(int, Class) for (int i = 0; i < expected.length; i++) { final int pos = permutation[i]; final Value e = expected[pos]; if (e == null) { final Value retrieved = rec.getField(pos, IntValue.class); if (retrieved != null) { Assert.fail("Value at position " + pos + " expected to be null in " + Arrays.toString(expected)); } } else { final Value retrieved = rec.getField(pos, e.getClass()); if (!(e.equals(retrieved))) { Assert.assertEquals("Wrong value at position " + pos + " in " + Arrays.toString(expected), e, retrieved); } } } // test getField(int, Value) for (int i = 0; i < expected.length; i++) { final int pos = permutation[i]; final Value e = expected[pos]; if (e == null) { final Value retrieved = rec.getField(pos, new IntValue()); if (retrieved != null) { Assert.fail("Value at position " + pos + " expected to be null in " + Arrays.toString(expected)); } } else { final Value retrieved = rec.getField(pos, e.getClass().newInstance()); if (!(e.equals(retrieved))) { Assert.assertEquals("Wrong value at position " + pos + " in " + Arrays.toString(expected), e, retrieved); } } } // test getFieldInto(Value) for (int i = 0; i < expected.length; i++) { final int pos = permutation[i]; final Value e = expected[pos]; if (e == null) { if (rec.getFieldInto(pos, new IntValue())) { Assert.fail("Value at position " + pos + " expected to be null in " + Arrays.toString(expected)); } } else { final Value retrieved = e.getClass().newInstance(); if (!rec.getFieldInto(pos, retrieved)) { Assert.fail("Value at position " + pos + " expected to be not null in " + Arrays.toString(expected)); } if (!(e.equals(retrieved))) { Assert.assertEquals("Wrong value at position " + pos + " in " + Arrays.toString(expected), e, retrieved); } } } } @Test public void testUnionFields() { try { final Value[][] values = new Value[][] { {new IntValue(56), null, new IntValue(-7628761)}, {null, new StringValue("Hellow Test!"), null}, {null, null, null, null, null, null, null, null}, {null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null}, {new IntValue(56), new IntValue(56), new IntValue(56), new IntValue(56), null, null, null}, {null, null, null, null, new IntValue(56), new IntValue(56), new IntValue(56)}, {new IntValue(43), new IntValue(42), new IntValue(41)}, {new IntValue(-463), new IntValue(-464), new IntValue(-465)} }; for (int i = 0; i < values.length - 1; i += 2) { testUnionFieldsForValues(values[i], values[i+1], this.rand); testUnionFieldsForValues(values[i+1], values[i], this.rand); } } catch (Throwable t) { Assert.fail("Test failed due to an exception: " + t.getMessage()); } } private void testUnionFieldsForValues(Value[] rec1fields, Value[] rec2fields, Random rnd) { // fully in binary sync Record rec1 = createRecord(rec1fields); Record rec2 = createRecord(rec2fields); rec1.updateBinaryRepresenation(); rec2.updateBinaryRepresenation(); rec1.unionFields(rec2); checkUnionedRecord(rec1, rec1fields, rec2fields); // fully not in binary sync rec1 = createRecord(rec1fields); rec2 = createRecord(rec2fields); rec1.unionFields(rec2); checkUnionedRecord(rec1, rec1fields, rec2fields); // one in binary sync rec1 = createRecord(rec1fields); rec2 = createRecord(rec2fields); rec1.updateBinaryRepresenation(); rec1.unionFields(rec2); checkUnionedRecord(rec1, rec1fields, rec2fields); // other in binary sync rec1 = createRecord(rec1fields); rec2 = createRecord(rec2fields); rec2.updateBinaryRepresenation(); rec1.unionFields(rec2); checkUnionedRecord(rec1, rec1fields, rec2fields); // both partially in binary sync rec1 = new Record(); int[] permutation1 = createPermutation(rnd, rec1fields.length); int[] permutation2 = createPermutation(rnd, rec2fields.length); int updatePos = rnd.nextInt(rec1fields.length + 1); for (int i = 0; i < rec1fields.length; i++) { if (i == updatePos) { rec1.updateBinaryRepresenation(); } final int pos = permutation1[i]; rec1.setField(pos, rec1fields[pos]); } if (updatePos == rec1fields.length) { rec1.updateBinaryRepresenation(); } updatePos = rnd.nextInt(rec2fields.length + 1); for (int i = 0; i < rec2fields.length; i++) { if (i == updatePos) { rec2.updateBinaryRepresenation(); } final int pos = permutation2[i]; rec2.setField(pos, rec2fields[pos]); } if (updatePos == rec2fields.length) { rec2.updateBinaryRepresenation(); } rec1.unionFields(rec2); checkUnionedRecord(rec1, rec1fields, rec2fields); } private static void checkUnionedRecord(Record union, Value[] rec1fields, Value[] rec2fields) { for (int i = 0; i < Math.max(rec1fields.length, rec2fields.length); i++) { // determine the expected value from the value arrays final Value expected; if (i < rec1fields.length) { if (i < rec2fields.length) { expected = rec1fields[i] == null ? rec2fields[i] : rec1fields[i]; } else { expected = rec1fields[i]; } } else { expected = rec2fields[i]; } // check value from record against expected value if (expected == null) { final Value retrieved = union.getField(i, IntValue.class); Assert.assertNull("Value at position " + i + " expected to be null in " + Arrays.toString(rec1fields) + " U " + Arrays.toString(rec2fields), retrieved); } else { final Value retrieved = union.getField(i, expected.getClass()); Assert.assertEquals("Wrong value at position " + i + " in " + Arrays.toString(rec1fields) + " U " + Arrays.toString(rec2fields), expected, retrieved); } } } // -------------------------------------------------------------------------------------------- // Utilities // -------------------------------------------------------------------------------------------- public static Record createRecord(Value[] fields) { final Record rec = new Record(); for (int i = 0; i < fields.length; i++) { rec.setField(i, fields[i]); } return rec; } public static Value[] createRandomValues(Random rnd, int minNum, int maxNum) { final int numFields = rnd.nextInt(maxNum - minNum + 1) + minNum; final Value[] values = new Value[numFields]; for (int i = 0; i < numFields; i++) { final int type = rnd.nextInt(7); switch (type) { case 0: values[i] = new IntValue(rnd.nextInt()); break; case 1: values[i] = new LongValue(rnd.nextLong()); break; case 2: values[i] = new DoubleValue(rnd.nextDouble()); break; case 3: values[i] = NullValue.getInstance(); break; case 4: values[i] = new StringValue(createRandomString(rnd)); break; default: values[i] = null; } } return values; } public static String createRandomString(Random rnd) { return createRandomString(rnd, rnd.nextInt(150)); } public static String createRandomString(Random rnd, int length) { final StringBuilder sb = new StringBuilder(); sb.ensureCapacity(length); for (int i = 0; i < length; i++) { sb.append((char) (rnd.nextInt(26) + 65)); } return sb.toString(); } public static int[] createPermutation(Random rnd, int length) { final int[] a = new int[length]; for (int i = 0; i < length; i++) { a[i] = i; } for (int i = 0; i < length; i++) { final int pos1 = rnd.nextInt(length); final int pos2 = rnd.nextInt(length); int temp = a[pos1]; a[pos1] = a[pos2]; a[pos2] = temp; } return a; } }