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.jellytools.modules.javacvs;
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 (running == false) {
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:
200: synchronized (this ) {
201: running = true;
202: notifyAll();
203: }
204:
205: while (true) {
206: try {
207: clientSocket = serverSocket.accept();
208: if (ignoreProbe == false) {
209: break;
210: }
211: ignoreProbe = false;
212: } catch (IOException e) {
213: throwable = e;
214: return;
215: }
216: }
217:
218: try {
219: socketOut = clientSocket.getOutputStream();
220: socketIn = clientSocket.getInputStream();
221: if (consumeInput()) {
222: return;
223: }
224: int nextByte = fakeDataStream.read();
225: while (nextByte != -1) {
226: if (outputCounter-- == 0) {
227: if ((simulationMode & SIMULATE_DROP) != 0) {
228: socketOut.flush();
229: socketOut.close();
230: }
231: if ((simulationMode & SIMULATE_OVERLOAD) != 0) {
232: clientSocket.shutdownOutput();
233: }
234: if ((simulationMode & SIMULATE_SLOWNESS) != 0) {
235: try {
236: Thread.sleep(5000);
237: } catch (InterruptedException e) {
238: throwable = e;
239: }
240: }
241: if ((simulationMode & (SIMULATE_OVERLOAD | SIMULATE_DROP)) != 0) {
242: consumeInputUntilStopped();
243: return;
244: }
245: }
246: socketOut.write(nextByte);
247: if (consumeInput()) {
248: return;
249: }
250: nextByte = fakeDataStream.read();
251: }
252: socketOut.flush();
253: // socketOut.close(); // need to propagate to client ASAP, otherwise all reads and available wait forever
254: // on the other hand it causes premature BrokenPipe signal because it
255: // immediately clears receiver's input buffers
256:
257: // do not close input streams prematurely
258: consumeInputUntilStopped();
259: } catch (IOException e) {
260: throwable = e;
261: return;
262: }
263: } finally {
264: try {
265: fakeDataStream.close();
266: } catch (IOException alreadyClosed) {
267: }
268: try {
269: if (socketIn != null)
270: socketIn.close();
271: } catch (IOException alreadyClosed) {
272: }
273: try {
274: if (socketOut != null)
275: socketOut.close();
276: } catch (IOException alreadyClosed) {
277: }
278: try {
279: if (requestsStream != null) {
280: requestsStream.flush();
281: requestsStream.close();
282: }
283: } catch (IOException alreadyClosed) {
284: }
285: }
286: }
287:
288: /**
289: * Stops server and optionaly rethrows internal server exception if any.
290: */
291: public synchronized void stop() throws Exception {
292: stopped = true;
293: notifyAll();
294: if (throwable != null) {
295: throw throwable;
296: }
297: }
298:
299: /** For diagnostics purpoes only. */
300: public String toString() {
301: StringWriter sw = new StringWriter();
302: PrintWriter ps = new PrintWriter(sw);
303: ps.write("PseudoCvsServer on " + serverSocket + "\n");
304: if (throwable != null) {
305: throwable.fillInStackTrace();
306: throwable.printStackTrace(ps);
307: }
308: ps.flush();
309: ps.close();
310: return sw.getBuffer().toString();
311: }
312:
313: /**
314: * Reads client input stream possibly simulating errors.
315: */
316: private boolean consumeInput() throws IOException {
317: int available = socketIn.available();
318: for (int i = 0; i < available; i++) {
319: if (inputCounter-- == 0) {
320: if ((simulationMode & SIMULATE_DROP) != 0) {
321: socketIn.close();
322: if (requestsStream != null) {
323: requestsStream.write("[PseudoCvsServer abort]"
324: .getBytes("utf8"));
325: }
326: }
327: if ((simulationMode & SIMULATE_OVERLOAD) != 0) {
328: clientSocket.shutdownInput();
329: if (requestsStream != null) {
330: requestsStream.write("[PseudoCvsServer abort]"
331: .getBytes("utf8"));
332: }
333: }
334: return true;
335: }
336: int octet = socketIn.read();
337: if (requestsStream != null) {
338: requestsStream.write(octet);
339: requestsStream.flush();
340: }
341: }
342: return false;
343: }
344:
345: private synchronized void consumeInputUntilStopped()
346: throws IOException {
347: while (stopped == false) {
348: try {
349: wait(100);
350: consumeInput();
351: } catch (InterruptedException e) {
352: throwable = e;
353: }
354: }
355: }
356: }
|