001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.test.cvsmodule;
043:
044: import java.io.*;
045: import java.net.ServerSocket;
046: import java.net.Socket;
047:
048: /**
049: * Unit testing CVS server implementation that provides
050: * constant replies coming from prepared files and
051: * simulates network and server overload failures.
052: *
053: * <p>Typical server usage in unit test sequence:
054: * <pre>
055: * InputStream in = getClass().getResourceAsStream("...");
056: * PseudoCvsServer cvss = new PseudoCvsServer(in);
057: * new Thread(cvss).start();
058: * String cvsRoot = cvss.getCvsRoot();
059: * <client operations>
060: * cvss.stop(); // check test failure
061: * <tested client asserts>
062: * </pre>
063: *
064: * <p>Fake input and output streams can be on Unix systems
065: * catched using <tt>nc</tt> program. To catch command line
066: * <tt>cvs</tt>:
067: * <ul>
068: * <li>outgoing requests stream use <pre>nc -l -p $3000 | tee $requests.log</pre> and
069: * <pre>cvs -d :pserver:$ano@127.0.0.1:$3000/$cvs -z0 $whateEverCommand</pre>
070: * <li>incoming responses stream use <pre>nc $cvs.netbeans.org $2401 | tee $reponses.log</pre>
071: * </ul>
072: *
073: * @author Petr Kuzel
074: */
075: public final class PseudoCvsServer implements Runnable {
076:
077: private final int SIMULATE_SLOWNESS = 1;
078: private final int SIMULATE_OVERLOAD = 2;
079: private final int SIMULATE_DROP = 4;
080:
081: private final InputStream fakeDataStream;
082: private OutputStream requestsStream;
083: private final ServerSocket serverSocket;
084:
085: private Socket clientSocket;
086: private OutputStream socketOut;
087: private InputStream socketIn;
088:
089: private int outputCounter = -1;
090: private int inputCounter = -1;
091: private int simulationMode;
092:
093: private Exception throwable;
094: private boolean stopped;
095: private boolean running;
096: private boolean ignoreProbe;
097:
098: /**
099: * Creates new server that replies with given data.
100: * @param in input stream that is consumend and <b>closed</b>
101: * once server runnable terminates.
102: *
103: * @throws IOException if cannot create server socket
104: */
105: public PseudoCvsServer(InputStream in) throws IOException {
106: try {
107: this .fakeDataStream = in;
108: serverSocket = new ServerSocket();
109: serverSocket.bind(null, 2);
110: } catch (IOException ex) {
111: in.close();
112: throw ex;
113: }
114: }
115:
116: /**
117: * returns port that accepts client requests.
118: */
119: public int getPort() {
120: return serverSocket.getLocalPort();
121: }
122:
123: /**
124: * Utility method returning typical CVSRoot sutable for
125: * local CVSClient testing.
126: *
127: * @return <code>":pserver:anoncvs@127.0.0.1:" + getPort() + "/cvs"</code>
128: */
129: public synchronized String getCvsRoot() {
130: try {
131: while (!isRunnuing()) {
132: this .wait();
133: }
134: } catch (InterruptedException e) {
135: }
136: return ":pserver:anoncvs@127.0.0.1:" + getPort() + "/cvs";
137: }
138:
139: /**
140: * Enters hard network failure simulation mode, silentry
141: * dropping down connection after specified number of in/outgoing bytes.
142: *
143: * @param write specifies number of bytes send before
144: * closing socket output stream. -1 for unlimited.
145: * @param read specifies number of bytes received before
146: * closing socket input stream. -1 for unlimited.
147: */
148: public void simulateNetworkFailure(int write, int read) {
149: simulationMode |= SIMULATE_DROP;
150: outputCounter = write;
151: inputCounter = read;
152: }
153:
154: /**
155: * Enters server overload simulation mode.
156: * Server properly closes streams sending TCP signals to client.
157: *
158: * @param write specifies number of bytes send before
159: * shuting down socket output stream. -1 for unlimited.
160: * @param read specifies number of bytes received before
161: * shuting down socket input stream. -1 for unlimited.
162: */
163: public void simulateServerOverload(int write, int read) {
164: simulationMode |= SIMULATE_OVERLOAD;
165: outputCounter = write;
166: inputCounter = read;
167: }
168:
169: public void simulateSlowNetwork(int write, int read) {
170: simulationMode |= SIMULATE_SLOWNESS;
171: outputCounter = write;
172: inputCounter = read;
173: }
174:
175: /**
176: * Enters ignore very first connect mode (connection probe).
177: * It means that actual data are send out to second requestor.
178: */
179: public void ignoreProbe() {
180: ignoreProbe = true;
181: }
182:
183: /**
184: * Logs server input intu specified stream.
185: *
186: * @param out log stream. The stream is closed on server termination.
187: */
188: public void logRequests(OutputStream out) {
189: requestsStream = out;
190: }
191:
192: /**
193: * Entry point, starts listening at port and sends out
194: * predefined replies. HAndles only first request.
195: */
196: public void run() {
197:
198: try {
199: setRunning(true);
200:
201: while (true) {
202: try {
203: clientSocket = serverSocket.accept();
204: if (ignoreProbe == false) {
205: break;
206: }
207: ignoreProbe = false;
208: } catch (IOException e) {
209: throwable = e;
210: return;
211: }
212: }
213:
214: try {
215: socketOut = clientSocket.getOutputStream();
216: socketIn = clientSocket.getInputStream();
217: if (consumeInput()) {
218: return;
219: }
220: int nextByte = fakeDataStream.read();
221: while (nextByte != -1) {
222: if (outputCounter-- == 0) {
223: if ((simulationMode & SIMULATE_DROP) != 0) {
224: socketOut.flush();
225: socketOut.close();
226: }
227: if ((simulationMode & SIMULATE_OVERLOAD) != 0) {
228: clientSocket.shutdownOutput();
229: }
230: if ((simulationMode & SIMULATE_SLOWNESS) != 0) {
231: try {
232: Thread.sleep(5000);
233: } catch (InterruptedException e) {
234: throwable = e;
235: }
236: }
237: if ((simulationMode & (SIMULATE_OVERLOAD | SIMULATE_DROP)) != 0) {
238: consumeInputUntilStopped();
239: return;
240: }
241: }
242: socketOut.write(nextByte);
243: if (consumeInput()) {
244: return;
245: }
246: nextByte = fakeDataStream.read();
247: }
248: socketOut.flush();
249: // socketOut.close(); // need to propagate to client ASAP, otherwise all reads and available wait forever
250: // on the other hand it causes premature BrokenPipe signal because it
251: // immediately clears receiver's input buffers
252:
253: // do not close input streams prematurely
254: consumeInputUntilStopped();
255: } catch (IOException e) {
256: throwable = e;
257: return;
258: }
259: } finally {
260: try {
261: fakeDataStream.close();
262: } catch (IOException alreadyClosed) {
263: }
264: try {
265: if (socketIn != null)
266: socketIn.close();
267: } catch (IOException alreadyClosed) {
268: }
269: try {
270: if (socketOut != null)
271: socketOut.close();
272: } catch (IOException alreadyClosed) {
273: }
274: try {
275: if (requestsStream != null) {
276: requestsStream.flush();
277: requestsStream.close();
278: }
279: } catch (IOException alreadyClosed) {
280: }
281: }
282: }
283:
284: /**
285: * Stops server and optionaly rethrows internal server exception if any.
286: */
287: public synchronized void stop() throws Exception {
288: stopped = true;
289: notifyAll();
290: if (throwable != null) {
291: throw throwable;
292: }
293: }
294:
295: /** For diagnostics purpoes only. */
296: public String toString() {
297: StringWriter sw = new StringWriter();
298: PrintWriter ps = new PrintWriter(sw);
299: ps.write("PseudoCvsServer on " + serverSocket + "\n");
300: if (throwable != null) {
301: throwable.fillInStackTrace();
302: throwable.printStackTrace(ps);
303: }
304: ps.flush();
305: ps.close();
306: return sw.getBuffer().toString();
307: }
308:
309: /**
310: * Reads client input stream possibly simulating errors.
311: */
312: private boolean consumeInput() throws IOException {
313: int available = socketIn.available();
314: for (int i = 0; i < available; i++) {
315: if (inputCounter-- == 0) {
316: if ((simulationMode & SIMULATE_DROP) != 0) {
317: socketIn.close();
318: if (requestsStream != null) {
319: requestsStream.write("[PseudoCvsServer abort]"
320: .getBytes("utf8"));
321: }
322: }
323: if ((simulationMode & SIMULATE_OVERLOAD) != 0) {
324: clientSocket.shutdownInput();
325: if (requestsStream != null) {
326: requestsStream.write("[PseudoCvsServer abort]"
327: .getBytes("utf8"));
328: }
329: }
330: return true;
331: }
332: int octet = socketIn.read();
333: if (requestsStream != null) {
334: requestsStream.write(octet);
335: requestsStream.flush();
336: }
337: }
338: return false;
339: }
340:
341: private synchronized void consumeInputUntilStopped()
342: throws IOException {
343: while (stopped == false) {
344: try {
345: wait(100);
346: consumeInput();
347: } catch (InterruptedException e) {
348: throwable = e;
349: }
350: }
351: }
352:
353: public synchronized boolean isRunnuing() {
354: return running;
355: }
356:
357: public synchronized void setRunning(boolean value) {
358: running = value;
359: if (value) {
360: notifyAll();
361: }
362: }
363: }
|