Skip to content

Commit 45c90c4

Browse files
committed
Use memory-backed streams in tests
Sometimes an actual file is needed, but sometimes it's not. In the case where it isn't, we can use an in-memory `InputFile`, `OutputFile` implementation.
1 parent 66adec7 commit 45c90c4

7 files changed

Lines changed: 258 additions & 23 deletions

File tree

core/src/test/java/org/apache/iceberg/TestManifestListVersions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.iceberg.avro.AvroSchemaUtil;
3131
import org.apache.iceberg.io.CloseableIterable;
3232
import org.apache.iceberg.io.FileAppender;
33+
import org.apache.iceberg.io.InMemoryOutputFile;
3334
import org.apache.iceberg.io.InputFile;
3435
import org.apache.iceberg.io.OutputFile;
3536
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
@@ -251,7 +252,7 @@ public void testManifestsPartitionSummary() throws IOException {
251252
}
252253

253254
private InputFile writeManifestList(ManifestFile manifest, int formatVersion) throws IOException {
254-
OutputFile manifestList = Files.localOutput(temp.newFile());
255+
OutputFile manifestList = new InMemoryOutputFile();
255256
try (FileAppender<ManifestFile> writer = ManifestLists.write(
256257
formatVersion, manifestList, SNAPSHOT_ID, SNAPSHOT_ID - 1, formatVersion > 1 ? SEQ_NUM : 0)) {
257258
writer.add(manifest);

core/src/test/java/org/apache/iceberg/TestManifestWriterVersions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.iceberg.io.CloseableIterable;
2525
import org.apache.iceberg.io.FileAppender;
2626
import org.apache.iceberg.io.FileIO;
27+
import org.apache.iceberg.io.InMemoryOutputFile;
2728
import org.apache.iceberg.io.InputFile;
2829
import org.apache.iceberg.io.OutputFile;
2930
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
@@ -235,7 +236,7 @@ void checkRewrittenManifest(ManifestFile manifest, long expectedSequenceNumber,
235236
}
236237

237238
private InputFile writeManifestList(ManifestFile manifest, int formatVersion) throws IOException {
238-
OutputFile manifestList = Files.localOutput(temp.newFile());
239+
InMemoryOutputFile manifestList = new InMemoryOutputFile();
239240
try (FileAppender<ManifestFile> writer = ManifestLists.write(
240241
formatVersion, manifestList, SNAPSHOT_ID, SNAPSHOT_ID - 1, formatVersion > 1 ? SEQUENCE_NUMBER : 0)) {
241242
writer.add(manifest);

core/src/test/java/org/apache/iceberg/TestScansAndSchemaEvolution.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.apache.iceberg.avro.RandomAvroData;
2929
import org.apache.iceberg.expressions.Expressions;
3030
import org.apache.iceberg.io.FileAppender;
31+
import org.apache.iceberg.io.InMemoryOutputFile;
32+
import org.apache.iceberg.io.OutputFile;
3133
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
3234
import org.apache.iceberg.types.Types;
3335
import org.junit.After;
@@ -65,11 +67,11 @@ public TestScansAndSchemaEvolution(int formatVersion) {
6567
@Rule
6668
public TemporaryFolder temp = new TemporaryFolder();
6769

68-
private DataFile createDataFile(File dataPath, String partValue) throws IOException {
70+
private DataFile createDataFile(String partValue) throws IOException {
6971
List<GenericData.Record> expected = RandomAvroData.generate(SCHEMA, 100, 0L);
7072

71-
File dataFile = new File(dataPath, FileFormat.AVRO.addExtension(UUID.randomUUID().toString()));
72-
try (FileAppender<GenericData.Record> writer = Avro.write(Files.localOutput(dataFile))
73+
OutputFile dataFile = new InMemoryOutputFile(FileFormat.AVRO.addExtension(UUID.randomUUID().toString()));
74+
try (FileAppender<GenericData.Record> writer = Avro.write(dataFile)
7375
.schema(SCHEMA)
7476
.named("test")
7577
.build()) {
@@ -82,7 +84,7 @@ private DataFile createDataFile(File dataPath, String partValue) throws IOExcept
8284
PartitionData partition = new PartitionData(SPEC.partitionType());
8385
partition.set(0, partValue);
8486
return DataFiles.builder(SPEC)
85-
.withInputFile(Files.localInput(dataFile))
87+
.withInputFile(dataFile.toInputFile())
8688
.withPartition(partition)
8789
.withRecordCount(100)
8890
.build();
@@ -96,13 +98,12 @@ public void cleanupTables() {
9698
@Test
9799
public void testPartitionSourceRename() throws IOException {
98100
File location = temp.newFolder();
99-
File dataLocation = new File(location, "data");
100101
Assert.assertTrue(location.delete()); // should be created by table create
101102

102103
Table table = TestTables.create(location, "test", SCHEMA, SPEC, formatVersion);
103104

104-
DataFile fileOne = createDataFile(dataLocation, "one");
105-
DataFile fileTwo = createDataFile(dataLocation, "two");
105+
DataFile fileOne = createDataFile("one");
106+
DataFile fileTwo = createDataFile("two");
106107

107108
table.newAppend()
108109
.appendFile(fileOne)

core/src/test/java/org/apache/iceberg/avro/TestAvroDeleteWriters.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.apache.iceberg.data.avro.DataWriter;
3636
import org.apache.iceberg.deletes.EqualityDeleteWriter;
3737
import org.apache.iceberg.deletes.PositionDeleteWriter;
38+
import org.apache.iceberg.io.InMemoryOutputFile;
3839
import org.apache.iceberg.io.OutputFile;
3940
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
4041
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
@@ -73,9 +74,7 @@ public void createDeleteRecords() {
7374

7475
@Test
7576
public void testEqualityDeleteWriter() throws IOException {
76-
File deleteFile = temp.newFile();
77-
78-
OutputFile out = Files.localOutput(deleteFile);
77+
OutputFile out = new InMemoryOutputFile();
7978
EqualityDeleteWriter<Record> deleteWriter = Avro.writeDeletes(out)
8079
.createWriterFunc(DataWriter::create)
8180
.overwrite()
@@ -108,8 +107,6 @@ public void testEqualityDeleteWriter() throws IOException {
108107

109108
@Test
110109
public void testPositionDeleteWriter() throws IOException {
111-
File deleteFile = temp.newFile();
112-
113110
Schema deleteSchema = new Schema(
114111
MetadataColumns.DELETE_FILE_PATH,
115112
MetadataColumns.DELETE_FILE_POS,
@@ -119,7 +116,7 @@ public void testPositionDeleteWriter() throws IOException {
119116
GenericRecord posDelete = GenericRecord.create(deleteSchema);
120117
List<Record> expectedDeleteRecords = Lists.newArrayList();
121118

122-
OutputFile out = Files.localOutput(deleteFile);
119+
OutputFile out = new InMemoryOutputFile();
123120
PositionDeleteWriter<Record> deleteWriter = Avro.writeDeletes(out)
124121
.createWriterFunc(DataWriter::create)
125122
.overwrite()

core/src/test/java/org/apache/iceberg/avro/TestGenericAvro.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,21 @@
1919

2020
package org.apache.iceberg.avro;
2121

22-
import java.io.File;
2322
import java.io.IOException;
2423
import java.util.List;
2524
import org.apache.avro.generic.GenericData.Record;
26-
import org.apache.iceberg.Files;
2725
import org.apache.iceberg.Schema;
2826
import org.apache.iceberg.io.FileAppender;
27+
import org.apache.iceberg.io.InMemoryOutputFile;
2928
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
30-
import org.junit.Assert;
3129

3230
public class TestGenericAvro extends AvroDataTest {
3331
@Override
3432
protected void writeAndValidate(Schema schema) throws IOException {
3533
List<Record> expected = RandomAvroData.generate(schema, 100, 0L);
3634

37-
File testFile = temp.newFile();
38-
Assert.assertTrue("Delete should succeed", testFile.delete());
39-
40-
try (FileAppender<Record> writer = Avro.write(Files.localOutput(testFile))
35+
InMemoryOutputFile outputFile = new InMemoryOutputFile();
36+
try (FileAppender<Record> writer = Avro.write(outputFile)
4137
.schema(schema)
4238
.named("test")
4339
.build()) {
@@ -47,7 +43,7 @@ protected void writeAndValidate(Schema schema) throws IOException {
4743
}
4844

4945
List<Record> rows;
50-
try (AvroIterable<Record> reader = Avro.read(Files.localInput(testFile))
46+
try (AvroIterable<Record> reader = Avro.read(outputFile.toInputFile())
5147
.project(schema)
5248
.build()) {
5349
rows = Lists.newArrayList(reader);
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.io;
21+
22+
import java.io.ByteArrayInputStream;
23+
import java.io.IOException;
24+
import java.util.UUID;
25+
26+
import static java.util.Objects.requireNonNull;
27+
import static org.apache.iceberg.relocated.com.google.common.base.Preconditions.checkState;
28+
29+
public class InMemoryInputFile implements InputFile {
30+
31+
private final String location;
32+
private final byte[] contents;
33+
34+
public InMemoryInputFile(byte[] contents) {
35+
this("memory:" + UUID.randomUUID(), contents);
36+
}
37+
38+
public InMemoryInputFile(String location, byte[] contents) {
39+
this.location = requireNonNull(location, "location is null");
40+
this.contents = requireNonNull(contents, "contents is null").clone();
41+
}
42+
43+
@Override
44+
public long getLength() {
45+
return contents.length;
46+
}
47+
48+
@Override
49+
public SeekableInputStream newStream() {
50+
return new InMemorySeekableInputStream(contents);
51+
}
52+
53+
@Override
54+
public String location() {
55+
return location;
56+
}
57+
58+
@Override
59+
public boolean exists() {
60+
return true;
61+
}
62+
63+
private static class InMemorySeekableInputStream extends SeekableInputStream {
64+
65+
private final int length;
66+
private final ByteArrayInputStream delegate;
67+
68+
InMemorySeekableInputStream(byte[] contents) {
69+
this.length = contents.length;
70+
this.delegate = new ByteArrayInputStream(contents);
71+
}
72+
73+
@Override
74+
public long getPos() throws IOException {
75+
return length - delegate.available();
76+
}
77+
78+
@Override
79+
public void seek(long newPos) throws IOException {
80+
delegate.reset();
81+
checkState(delegate.skip(newPos) == newPos, "Invalid position %s within stream of length %s", newPos, length);
82+
}
83+
84+
@Override
85+
public int read() {
86+
return delegate.read();
87+
}
88+
89+
@Override
90+
public int read(byte[] b) throws IOException {
91+
return delegate.read(b);
92+
}
93+
94+
@Override
95+
public int read(byte[] b, int off, int len) {
96+
return delegate.read(b, off, len);
97+
}
98+
99+
@Override
100+
public long skip(long n) {
101+
return delegate.skip(n);
102+
}
103+
104+
@Override
105+
public int available() {
106+
return delegate.available();
107+
}
108+
109+
@Override
110+
public boolean markSupported() {
111+
throw new UnsupportedOperationException();
112+
}
113+
114+
@Override
115+
public void mark(int readAheadLimit) {
116+
// We use mark to implement seek
117+
throw new UnsupportedOperationException();
118+
}
119+
120+
@Override
121+
public void reset() {
122+
delegate.reset();
123+
}
124+
125+
@Override
126+
public void close() throws IOException {
127+
delegate.close();
128+
}
129+
}
130+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.io;
21+
22+
import java.io.ByteArrayOutputStream;
23+
import java.io.IOException;
24+
import java.util.UUID;
25+
26+
import static java.util.Objects.requireNonNull;
27+
28+
public class InMemoryOutputFile implements OutputFile {
29+
30+
private final String location;
31+
32+
private boolean exists;
33+
private ByteArrayOutputStream contents;
34+
35+
public InMemoryOutputFile() {
36+
this("memory:" + UUID.randomUUID());
37+
}
38+
39+
public InMemoryOutputFile(String location) {
40+
this.location = requireNonNull(location, "location is null");
41+
}
42+
43+
@Override
44+
public PositionOutputStream create() {
45+
if (exists) {
46+
throw new RuntimeException("Already exists");
47+
}
48+
return createOrOverwrite();
49+
}
50+
51+
@Override
52+
public PositionOutputStream createOrOverwrite() {
53+
exists = true;
54+
contents = new ByteArrayOutputStream();
55+
return new InMemoryPositionOutputStream(contents);
56+
}
57+
58+
@Override
59+
public String location() {
60+
return location;
61+
}
62+
63+
@Override
64+
public InputFile toInputFile() {
65+
return new InMemoryInputFile(location(), getContents());
66+
}
67+
68+
public byte[] getContents() {
69+
return contents.toByteArray();
70+
}
71+
72+
private static class InMemoryPositionOutputStream extends PositionOutputStream {
73+
private final ByteArrayOutputStream delegate;
74+
75+
InMemoryPositionOutputStream(ByteArrayOutputStream delegate) {
76+
this.delegate = requireNonNull(delegate, "delegate is null");
77+
}
78+
79+
@Override
80+
public long getPos() {
81+
return delegate.size();
82+
}
83+
84+
@Override
85+
public void write(int b) {
86+
delegate.write(b);
87+
}
88+
89+
@Override
90+
public void write(byte[] b) throws IOException {
91+
delegate.write(b);
92+
}
93+
94+
@Override
95+
public void write(byte[] b, int off, int len) {
96+
delegate.write(b, off, len);
97+
}
98+
99+
@Override
100+
public void flush() throws IOException {
101+
delegate.flush();
102+
}
103+
104+
@Override
105+
public void close() throws IOException {
106+
delegate.close();
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)