// Copyright 2014 The Bazel Authors. All rights reserved. // // 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 com.google.devtools.build.lib.util.io; import com.google.common.io.ByteStreams; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * Exercise {@link StreamMultiplexer} in a parallel setting and ensure there's * no corruption. */ @RunWith(JUnit4.class) public class StreamMultiplexerParallelStressTest { /** * Characters that could likely cause corruption (they're used as control * characters). */ char[] toughCharsToTry = {'\n', '@', '1', '2', '\0', '0'}; /** * We use a demultiplexer as a simple sanity checker only - that is, we don't * care what the demultiplexer writes, but we are taking advantage of its * built in error checking. */ OutputStream devNull = ByteStreams.nullOutputStream(); StreamDemultiplexer demux = new StreamDemultiplexer((byte) 1, devNull, devNull, devNull); /** * The multiplexer under test. */ StreamMultiplexer mux = new StreamMultiplexer(demux); /** * Streams is the out / err / control output streams of the multiplexer which * we will write to in parallel. */ OutputStream[] streams = { mux.createStdout(), mux.createStderr(), mux.createControl()}; /** * We will create a bunch of threads that write random data to the streams of * the mux. */ class RandomDataPump implements Callable<Object> { private Random random; public RandomDataPump(int threadId) { random = new Random(threadId * 0xdeadbeefL); } @Override public Object call() throws Exception { Thread.yield(); OutputStream out = streams[random.nextInt(2)]; for (int i = 0; i < 10000; i++) { switch (random.nextInt(5)) { case 0: out.write(random.nextInt()); break; case 1: int index = random.nextInt(toughCharsToTry.length); out.write(toughCharsToTry[index]); break; case 2: byte[] buffer = new byte[random.nextInt(312)]; random.nextBytes(buffer); out.write(buffer); break; case 3: out.flush(); break; case 4: out = streams[random.nextInt(3)]; break; } } return null; } } @Test public void testSingleThreadedStress() throws Exception { new RandomDataPump(1).call(); } @Test public void testMultiThreadedStress() throws InterruptedException, ExecutionException { ExecutorService service = Executors.newFixedThreadPool(50); List<Future<?>> futures = new ArrayList<>(); for (int threadId = 0; threadId < 50; threadId++) { futures.add(service.submit(new RandomDataPump(threadId))); } for (Future<?> future : futures) { future.get(); } } }