View Javadoc
1   /*
2    * Copyright (C) 2009-2010, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.util.io;
12  
13  import static org.junit.Assert.assertArrayEquals;
14  import static org.junit.Assert.assertEquals;
15  import static org.junit.Assert.assertFalse;
16  import static org.junit.Assert.assertTrue;
17  import static org.junit.Assert.fail;
18  
19  import java.io.IOException;
20  import java.io.InterruptedIOException;
21  import java.io.OutputStream;
22  import java.io.PipedInputStream;
23  import java.io.PipedOutputStream;
24  import java.util.Arrays;
25  import java.util.List;
26  
27  import org.eclipse.jgit.util.IO;
28  import org.junit.After;
29  import org.junit.Before;
30  import org.junit.Test;
31  
32  public class TimeoutOutputStreamTest {
33  	private static final int timeout = 250;
34  
35  	private PipedOutputStream out;
36  
37  	private FullPipeInputStream in;
38  
39  	private InterruptTimer timer;
40  
41  	private TimeoutOutputStream os;
42  
43  	private long start;
44  
45  	@Before
46  	public void setUp() throws Exception {
47  		out = new PipedOutputStream();
48  		in = new FullPipeInputStream(out);
49  		timer = new InterruptTimer();
50  		os = new TimeoutOutputStream(out, timer);
51  		os.setTimeout(timeout);
52  	}
53  
54  	@After
55  	public void tearDown() throws Exception {
56  		timer.terminate();
57  		for (Thread t : active())
58  			assertFalse(t instanceof InterruptTimer.AlarmThread);
59  	}
60  
61  	@Test
62  	public void testTimeout_writeByte_Success1() throws IOException {
63  		in.free(1);
64  		os.write('a');
65  		in.want(1);
66  		assertEquals('a', in.read());
67  	}
68  
69  	@Test
70  	public void testTimeout_writeByte_Success2() throws IOException {
71  		final byte[] exp = new byte[] { 'a', 'b', 'c' };
72  		final byte[] act = new byte[exp.length];
73  		in.free(exp.length);
74  		os.write(exp[0]);
75  		os.write(exp[1]);
76  		os.write(exp[2]);
77  		in.want(exp.length);
78  		in.read(act);
79  		assertArrayEquals(exp, act);
80  	}
81  
82  	@Test
83  	public void testTimeout_writeByte_Timeout() throws IOException {
84  		beginWrite();
85  		try {
86  			os.write('\n');
87  			fail("incorrectly write a byte");
88  		} catch (InterruptedIOException e) {
89  			// expected
90  		}
91  		assertTimeout();
92  	}
93  
94  	@Test
95  	public void testTimeout_writeBuffer_Success1() throws IOException {
96  		final byte[] exp = new byte[] { 'a', 'b', 'c' };
97  		final byte[] act = new byte[exp.length];
98  		in.free(exp.length);
99  		os.write(exp);
100 		in.want(exp.length);
101 		in.read(act);
102 		assertArrayEquals(exp, act);
103 	}
104 
105 	@Test
106 	public void testTimeout_writeBuffer_Timeout() throws IOException {
107 		beginWrite();
108 		try {
109 			os.write(new byte[512]);
110 			fail("incorrectly wrote bytes");
111 		} catch (InterruptedIOException e) {
112 			// expected
113 		}
114 		assertTimeout();
115 	}
116 
117 	@Test
118 	public void testTimeout_flush_Success() throws IOException {
119 		final boolean[] called = new boolean[1];
120 		os = new TimeoutOutputStream(new OutputStream() {
121 			@Override
122 			public void write(int b) throws IOException {
123 				fail("should not have written");
124 			}
125 
126 			@Override
127 			public void flush() throws IOException {
128 				called[0] = true;
129 			}
130 		}, timer);
131 		os.setTimeout(timeout);
132 		os.flush();
133 		assertTrue(called[0]);
134 	}
135 
136 	@Test
137 	public void testTimeout_flush_Timeout() throws IOException {
138 		final boolean[] called = new boolean[1];
139 		os = new TimeoutOutputStream(new OutputStream() {
140 			@Override
141 			public void write(int b) throws IOException {
142 				fail("should not have written");
143 			}
144 
145 			@Override
146 			public void flush() throws IOException {
147 				called[0] = true;
148 				for (;;) {
149 					try {
150 						Thread.sleep(1000);
151 					} catch (InterruptedException e) {
152 						InterruptedIOException e1 = new InterruptedIOException();
153 						e1.initCause(e);
154 						throw e1;
155 					}
156 				}
157 			}
158 		}, timer);
159 		os.setTimeout(timeout);
160 
161 		beginWrite();
162 		try {
163 			os.flush();
164 			fail("incorrectly flushed");
165 		} catch (InterruptedIOException e) {
166 			// expected
167 		}
168 		assertTimeout();
169 		assertTrue(called[0]);
170 	}
171 
172 	@Test
173 	public void testTimeout_close_Success() throws IOException {
174 		final boolean[] called = new boolean[1];
175 		os = new TimeoutOutputStream(new OutputStream() {
176 			@Override
177 			public void write(int b) throws IOException {
178 				fail("should not have written");
179 			}
180 
181 			@Override
182 			public void close() throws IOException {
183 				called[0] = true;
184 			}
185 		}, timer);
186 		os.setTimeout(timeout);
187 		os.close();
188 		assertTrue(called[0]);
189 	}
190 
191 	@Test
192 	public void testTimeout_close_Timeout() throws IOException {
193 		final boolean[] called = new boolean[1];
194 		os = new TimeoutOutputStream(new OutputStream() {
195 			@Override
196 			public void write(int b) throws IOException {
197 				fail("should not have written");
198 			}
199 
200 			@Override
201 			public void close() throws IOException {
202 				called[0] = true;
203 				for (;;) {
204 					try {
205 						Thread.sleep(1000);
206 					} catch (InterruptedException e) {
207 						InterruptedIOException e1 = new InterruptedIOException();
208 						e1.initCause(e);
209 						throw e1;
210 					}
211 				}
212 			}
213 		}, timer);
214 		os.setTimeout(timeout);
215 
216 		beginWrite();
217 		try {
218 			os.close();
219 			fail("incorrectly closed");
220 		} catch (InterruptedIOException e) {
221 			// expected
222 		}
223 		assertTimeout();
224 		assertTrue(called[0]);
225 	}
226 
227 	private void beginWrite() {
228 		start = now();
229 	}
230 
231 	private void assertTimeout() {
232 		// Our timeout was supposed to be ~250 ms. Since this is a timing
233 		// test we can't assume we spent *exactly* the timeout period, as
234 		// there may be other activity going on in the system. Instead we
235 		// look for the delta between the start and end times to be within
236 		// 50 ms of the expected timeout.
237 		//
238 		final long wait = now() - start;
239 		assertTrue("waited only " + wait + " ms", timeout - wait < 50);
240 	}
241 
242 	private static List<Thread> active() {
243 		Thread[] all = new Thread[16];
244 		int n = Thread.currentThread().getThreadGroup().enumerate(all);
245 		while (n == all.length) {
246 			all = new Thread[all.length * 2];
247 			n = Thread.currentThread().getThreadGroup().enumerate(all);
248 		}
249 		return Arrays.asList(all).subList(0, n);
250 	}
251 
252 	private static long now() {
253 		return System.currentTimeMillis();
254 	}
255 
256 	private static final class FullPipeInputStream extends PipedInputStream {
257 		FullPipeInputStream(PipedOutputStream src) throws IOException {
258 			super(src);
259 			src.write(new byte[PIPE_SIZE]);
260 		}
261 
262 		void want(int cnt) throws IOException {
263 			IO.skipFully(this, PIPE_SIZE - cnt);
264 		}
265 
266 		void free(int cnt) throws IOException {
267 			IO.skipFully(this, cnt);
268 		}
269 	}
270 }