/* * Copyright 2014-present Facebook, Inc. * * 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.facebook.buck.testutil.integration; import com.facebook.buck.util.CapturingPrintStream; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.martiansoftware.nailgun.NGClientListener; import com.martiansoftware.nailgun.NGConstants; import com.martiansoftware.nailgun.NGContext; import com.martiansoftware.nailgun.NGInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.Set; /** NGContext test double. */ public class TestContext extends NGContext implements Closeable { private Properties properties; private Set<NGClientListener> listeners; private CapturingPrintStream serverLog; private boolean addListeners; /** Simulates client that never disconnects, with normal system environment. */ public TestContext() { this(ImmutableMap.copyOf(System.getenv()), createDisconnectionStream(0), 0); addListeners = false; // Only track disconnections when input stream supplied. } /** Simulates client that never disconnects, with given environment. */ public TestContext(ImmutableMap<String, String> environment) { this(environment, createDisconnectionStream(0), 0); addListeners = false; // Only track disconnections when input stream supplied. } /** * Simulates client connected to given stream, with given timeout and environment. If stream * blocks for longer than timeout, or throws an exception, a client disconnection is triggered as * normal. */ public TestContext( ImmutableMap<String, String> environment, InputStream clientStream, long timeoutMillis) { serverLog = new CapturingPrintStream(); in = new NGInputStream( new DataInputStream(Preconditions.checkNotNull(clientStream)), new DataOutputStream(new ByteArrayOutputStream(0)), serverLog, (int) timeoutMillis); out = new CapturingPrintStream(); err = new CapturingPrintStream(); setExitStream(new CapturingPrintStream()); properties = new Properties(); for (String key : environment.keySet()) { properties.setProperty(key, environment.get(key)); } listeners = Sets.newHashSet(); addListeners = true; } @Override public Properties getEnv() { return properties; } @Override public void addClientListener(NGClientListener listener) { if (addListeners) { listeners.add(listener); super.addClientListener(listener); } } @Override public void removeClientListener(NGClientListener listener) { if (addListeners) { listeners.remove(listener); super.removeClientListener(listener); } } /** Generates heartbeat chunks at a given interval. */ public static InputStream createHeartBeatStream(final long heartbeatIntervalMillis) { return new InputStream() { private final int bytesPerHeartbeat = 5; private final long byteInterval = heartbeatIntervalMillis / bytesPerHeartbeat; @Override public int read() throws IOException { try { Thread.sleep(byteInterval); } catch (InterruptedException e) { throw new IOException(e); } return NGConstants.CHUNKTYPE_HEARTBEAT; } }; } /** * @param disconnectMillis duration to wait before generating IOException. * @return an InputStream which will wait and then simulate a client disconnection. */ public static InputStream createDisconnectionStream(final long disconnectMillis) { return new InputStream() { @Override public int read() throws IOException { try { Thread.sleep(disconnectMillis); } catch (InterruptedException e) { throw new IOException(e); } throw new IOException("Fake client disconnection."); } }; } @Override public void close() throws IOException { in.close(); } }