/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed 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.jboss.shrinkwrap.impl.base.exporter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
import java.util.logging.Logger;
import junit.framework.TestCase;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.asset.ByteArrayAsset;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.impl.base.io.IOUtil;
import org.junit.Test;
/**
* Stress test to ensure that archives exported as ZIPs which have size larger than available RAM can be processed,
* proving that we're buffering the encoding process.
*
* SHRINKWRAP-116
*
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
* @version $Revision: $
*/
public class ZipExporterStressTest {
// -------------------------------------------------------------------------------------||
// Class Members ----------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Logger
*/
private static final Logger log = Logger.getLogger(ZipExporterStressTest.class.getName());
/**
* 2^20
*/
private static BigDecimal MEGA = new BigDecimal(1024 * 1024);
// -------------------------------------------------------------------------------------||
// Tests ------------------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Ensures that we can export archives of large sizes without leading to {@link OutOfMemoryError}
*
* SHRINKWRAP-116
*/
@Test
public void exportHugeArchive() throws IOException {
// Log
log.info("exportHugeArchive");
log.info("This test may take awhile as it's intended to fill memory");
// Get an archive instance
final JavaArchive archive = ShrinkWrap.create(JavaArchive.class, "hugeArchive.jar");
// Approximate the free memory to start
final Runtime runtime = Runtime.getRuntime();
final long startFreeMemBytes = totalFreeMemory(runtime);
long beforeExportFreeMemBytes = startFreeMemBytes;
int counter = 0;
// Loop through and add a MB Asset
final String pathPrefix = "path";
// Fill up the archive until we've got only 30% of memory left
while (beforeExportFreeMemBytes > (startFreeMemBytes * .3)) {
archive.add(MegaByteAsset.newInstance(), pathPrefix + counter++);
System.gc(); // Signal to the VM to try to clean up a bit, not the most reliable, but makes this OK on my
// machine
beforeExportFreeMemBytes = totalFreeMemory(runtime);
log.info("Current Free Memory (MB): " + this.megaBytesFromBytes(beforeExportFreeMemBytes));
}
log.info("Wrote: " + archive.toString());
log.info("Started w/ free memory (MB): " + this.megaBytesFromBytes(startFreeMemBytes));
log.info("Free memory before export (MB): " + this.megaBytesFromBytes(beforeExportFreeMemBytes));
// Export; at this point we have less than 50% available memory so
// we can't carry the whole archive in RAM twice; this
// should ensure the ZIP impl uses an internal buffer
final InputStream in = archive.as(ZipExporter.class).exportAsInputStream();
final CountingOutputStream out = new CountingOutputStream();
// Copy, counting the final size of the exported ZIP
IOUtil.copyWithClose(in, out);
// Ensure we've just exported a ZIP larger than our available memory (proving we've buffered the encoding
// process)
TestCase.assertTrue("Test setup failed; we should be writing out more bytes than we have free memory",
out.bytesWritten > beforeExportFreeMemBytes);
log.info("Final ZIP export was: " + this.megaBytesFromBytes(out.bytesWritten) + " MB");
final long afterExportFreeMemBytes = totalFreeMemory(runtime);
log.info("Free memory after export (MB): " + this.megaBytesFromBytes(afterExportFreeMemBytes));
}
// -------------------------------------------------------------------------------------||
// Internal Helper Methods ------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Returns the number of MB the specified number of bytes represents
*
* @param bytes
* @return
*/
private BigDecimal megaBytesFromBytes(final long bytes) {
return new BigDecimal(bytes).divide(MEGA).setScale(2, RoundingMode.HALF_UP);
}
/**
* Obtains an estimate of the total amount of free memory available to the JVM
*
* @param runtime
* @return
*/
private static long totalFreeMemory(final Runtime runtime) {
return runtime.maxMemory() - runtime.totalMemory() + runtime.freeMemory();
}
/**
* An {@link Asset} which contains a megabyte of dummy data
*
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
*/
private static class MegaByteAsset extends ByteArrayAsset implements Asset {
/**
* Dummy megabyte
*/
private static int MEGA = 1024 * 1024;
private static final Random random = new Random();
private MegaByteAsset(final byte[] content) {
super(content);
}
static MegaByteAsset newInstance() {
/**
* Bytes must be random/distributed so that compressing these in ZIP isn't too efficient
*/
final byte[] content = new byte[MEGA];
random.nextBytes(content);
return new MegaByteAsset(content);
}
}
/**
* {@link OutputStream} which does nothing but count the bytes written
*
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
* @version $Revision: $
*/
private static class CountingOutputStream extends OutputStream {
long bytesWritten = 0;
@Override
public void write(int b) throws IOException {
bytesWritten++;
}
}
}