-
Notifications
You must be signed in to change notification settings - Fork 82
/
Copy pathharness.rs
251 lines (214 loc) · 7.07 KB
/
harness.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
// Copyright (c) 2023 CtrlC developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
#[cfg(unix)]
pub mod platform {
use std::io;
pub unsafe fn setup() -> io::Result<()> {
Ok(())
}
pub unsafe fn cleanup() -> io::Result<()> {
Ok(())
}
pub unsafe fn raise_ctrl_c() {
nix::sys::signal::raise(nix::sys::signal::SIGINT).unwrap();
}
pub unsafe fn print(fmt: ::std::fmt::Arguments) {
use self::io::Write;
let stdout = ::std::io::stdout();
stdout.lock().write_fmt(fmt).unwrap();
}
}
#[cfg(windows)]
pub mod platform {
use std::io;
use std::ptr;
use windows_sys::Win32::Foundation::{
GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileA, WriteFile, FILE_SHARE_WRITE, OPEN_EXISTING,
};
use windows_sys::Win32::System::Console::{
AllocConsole, AttachConsole, FreeConsole, GenerateConsoleCtrlEvent, GetConsoleMode,
GetStdHandle, SetStdHandle, ATTACH_PARENT_PROCESS, CTRL_C_EVENT, STD_ERROR_HANDLE,
STD_OUTPUT_HANDLE,
};
/// Stores a piped stdout handle or a cache that gets
/// flushed when we reattached to the old console.
enum Output {
Pipe(HANDLE),
Cached(Vec<u8>),
}
static mut OLD_OUT: *mut Output = 0 as *mut Output;
impl io::Write for Output {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match *self {
Output::Pipe(handle) => unsafe {
let mut n = 0u32;
if WriteFile(
handle,
buf.as_ptr(),
buf.len() as u32,
&mut n as *mut u32,
ptr::null_mut(),
) == 0
{
Err(io::Error::last_os_error())
} else {
Ok(n as usize)
}
},
Output::Cached(ref mut s) => s.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Output {
/// Stores current piped stdout or creates a new output cache that will
/// be written to stdout at a later time.
fn new() -> io::Result<Output> {
unsafe {
let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
if stdout == 0 || stdout == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let mut out = 0u32;
match GetConsoleMode(stdout, &mut out as *mut u32) {
0 => Ok(Output::Pipe(stdout)),
_ => Ok(Output::Cached(Vec::new())),
}
}
}
/// Set stdout/stderr and flush cache.
unsafe fn set_as_std(self) -> io::Result<()> {
let stdout = match self {
Output::Pipe(h) => h,
Output::Cached(_) => get_stdout()?,
};
if SetStdHandle(STD_OUTPUT_HANDLE, stdout) == 0 {
return Err(io::Error::last_os_error());
}
if SetStdHandle(STD_ERROR_HANDLE, stdout) == 0 {
return Err(io::Error::last_os_error());
}
match self {
Output::Pipe(_) => Ok(()),
Output::Cached(ref s) => {
// Write cached output
use self::io::Write;
let out = io::stdout();
out.lock().write_all(&s[..])?;
Ok(())
}
}
}
}
unsafe fn get_stdout() -> io::Result<HANDLE> {
let stdout = CreateFileA(
"CONOUT$\0".as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
0,
0 as HANDLE,
);
if stdout == 0 || stdout == INVALID_HANDLE_VALUE {
Err(io::Error::last_os_error())
} else {
Ok(stdout)
}
}
/// Detach from the current console and create a new one,
/// We do this because GenerateConsoleCtrlEvent() sends ctrl-c events
/// to all processes on the same console. We want events to be received
/// only by our process.
///
/// This breaks rust's stdout pre 1.18.0. Rust used to
/// [cache the std handles](https://github.com/rust-lang/rust/pull/40516)
///
pub unsafe fn setup() -> io::Result<()> {
let old_out = Output::new()?;
if FreeConsole() == 0 {
return Err(io::Error::last_os_error());
}
if AllocConsole() == 0 {
return Err(io::Error::last_os_error());
}
// AllocConsole will not always set stdout/stderr to the to the console buffer
// of the new terminal.
let stdout = get_stdout()?;
if SetStdHandle(STD_OUTPUT_HANDLE, stdout) == 0 {
return Err(io::Error::last_os_error());
}
if SetStdHandle(STD_ERROR_HANDLE, stdout) == 0 {
return Err(io::Error::last_os_error());
}
OLD_OUT = Box::into_raw(Box::new(old_out));
Ok(())
}
/// Reattach to the old console.
pub unsafe fn cleanup() -> io::Result<()> {
if FreeConsole() == 0 {
return Err(io::Error::last_os_error());
}
if AttachConsole(ATTACH_PARENT_PROCESS) == 0 {
return Err(io::Error::last_os_error());
}
Box::from_raw(OLD_OUT).set_as_std()?;
Ok(())
}
/// This will signal the whole process group.
pub unsafe fn raise_ctrl_c() {
assert!(GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) != 0);
}
/// Print to both consoles, this is not thread safe.
pub unsafe fn print(fmt: ::std::fmt::Arguments) {
use self::io::Write;
{
let stdout = io::stdout();
stdout.lock().write_fmt(fmt).unwrap();
}
{
assert!(!OLD_OUT.is_null());
(*OLD_OUT).write_fmt(fmt).unwrap();
}
}
}
macro_rules! run_tests {
( $($test_fn:ident),* ) => {
unsafe {
$(
harness::platform::print(format_args!("test {} ... ", stringify!($test_fn)));
$test_fn();
harness::platform::print(format_args!("ok\n"));
)*
}
}
}
pub fn run_harness(f: fn()) {
unsafe {
platform::setup().unwrap();
}
let default = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
unsafe {
platform::cleanup().unwrap();
}
(default)(info);
}));
println!("");
f();
println!("");
unsafe {
platform::cleanup().unwrap();
}
}