Switch to ctest crate
Extracted tests!
This commit is contained in:
parent
1846918fe2
commit
d11e9141b2
@ -25,4 +25,4 @@ build: false
|
||||
|
||||
test_script:
|
||||
- cargo test
|
||||
- cargo test --manifest-path libc-test/Cargo.toml
|
||||
- cargo run --manifest-path libc-test/Cargo.toml
|
||||
|
10
ci/run.sh
10
ci/run.sh
@ -3,19 +3,19 @@
|
||||
set -ex
|
||||
|
||||
TARGET=$1
|
||||
cargo test --manifest-path libc-test/Cargo.toml --no-run --target $TARGET
|
||||
cargo build --manifest-path libc-test/Cargo.toml --target $TARGET
|
||||
|
||||
if [ "$TARGET" = "arm-linux-androideabi" ]; then
|
||||
emulator @test -no-window &
|
||||
adb wait-for-device
|
||||
adb push /root/target/$TARGET/debug/all-* /data/test
|
||||
adb shell /data/test
|
||||
adb push /root/target/$TARGET/debug/libc-test /data/libc-test
|
||||
adb shell /data/libc-test
|
||||
elif [ "$TARGET" = "arm-unknown-linux-gnueabihf" ]; then
|
||||
qemu-arm -L /usr/arm-linux-gnueabihf libc-test/target/$TARGET/debug/all-*
|
||||
qemu-arm -L /usr/arm-linux-gnueabihf libc-test/target/$TARGET/debug/libc-test
|
||||
elif [ "$TARGET" = "mips-unknown-linux-gnu" ]; then
|
||||
# FIXME: this segfaults on travis, passes locally?
|
||||
#qemu-mips -L /usr/mips-linux-gnu libc-test/target/$TARGET/debug/all-*
|
||||
echo skip
|
||||
else
|
||||
cargo test --manifest-path libc-test/Cargo.toml --target $TARGET
|
||||
cargo run --manifest-path libc-test/Cargo.toml --target $TARGET
|
||||
fi
|
||||
|
100
libc-test/Cargo.lock
generated
100
libc-test/Cargo.lock
generated
@ -1,100 +0,0 @@
|
||||
[root]
|
||||
name = "libc-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gcc 0.3.13 (git+https://github.com/alexcrichton/gcc-rs)",
|
||||
"libc 0.1.10",
|
||||
"syntex_syntax 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "advapi32-sys"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "gcc"
|
||||
version = "0.3.13"
|
||||
source = "git+https://github.com/alexcrichton/gcc-rs#e429c775dcbb03eb049bcb7f7215cf8d9ee3b837"
|
||||
dependencies = [
|
||||
"advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.1.10"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syntex_syntax"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
@ -8,14 +8,4 @@ build = "build.rs"
|
||||
libc = { path = ".." }
|
||||
|
||||
[build-dependencies]
|
||||
syntex_syntax = "0.13.0"
|
||||
gcc = { git = "https://github.com/alexcrichton/gcc-rs" }
|
||||
|
||||
[lib]
|
||||
name = "libc_test"
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
[[test]]
|
||||
name = "all"
|
||||
harness = false
|
||||
ctest = { git = "https://github.com/alexcrichton/ctest" }
|
||||
|
@ -1,161 +1,87 @@
|
||||
extern crate gcc;
|
||||
extern crate syntex_syntax as syntax;
|
||||
extern crate ctest;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::BufWriter;
|
||||
use std::io::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use syntax::abi::Abi;
|
||||
use syntax::ast;
|
||||
use syntax::attr::{self, ReprAttr};
|
||||
use syntax::diagnostic::SpanHandler;
|
||||
use syntax::ext::base::SyntaxExtension;
|
||||
use syntax::ext::expand;
|
||||
use syntax::parse::token::{intern, InternedString};
|
||||
use syntax::parse::{self, ParseSess};
|
||||
use syntax::visit::{self, Visitor};
|
||||
fn main() {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
let windows = target.contains("windows");
|
||||
let mingw = target.contains("windows-gnu");
|
||||
let mut cfg = ctest::TestGenerator::new();
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
struct TestGenerator<'a> {
|
||||
target: String,
|
||||
rust: Box<Write>,
|
||||
c: Box<Write>,
|
||||
sh: &'a SpanHandler,
|
||||
structs: HashSet<String>,
|
||||
abi: Abi,
|
||||
tests: Vec<String>,
|
||||
}
|
||||
|
||||
struct StructFinder {
|
||||
structs: HashSet<String>,
|
||||
}
|
||||
|
||||
impl<'a> TestGenerator<'a> {
|
||||
fn defines(&self) -> Vec<&'static str> {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
// Pull in extra goodies on linux
|
||||
if self.target.contains("unknown-linux-gnu") {
|
||||
ret.push("_GNU_SOURCE");
|
||||
}
|
||||
|
||||
// MSVC doesn't have stdalign.h so get alignof ourselves
|
||||
if self.target.contains("msvc") {
|
||||
ret.push("alignof __alignof");
|
||||
}
|
||||
|
||||
// android also doesn't have stdalign.h so get alignof ourselves
|
||||
if self.target.contains("android") || self.target.contains("mips") {
|
||||
ret.push("alignof __alignof__");
|
||||
}
|
||||
|
||||
// Pull in extra goodies on mingw
|
||||
if self.target.contains("windows") {
|
||||
ret.push("_WIN32_WINNT 0x8000");
|
||||
}
|
||||
return ret
|
||||
// Pull in extra goodies on linux/mingw
|
||||
if target.contains("unknown-linux-gnu") {
|
||||
cfg.define("_GNU_SOURCE", None);
|
||||
} else if target.contains("windows") {
|
||||
cfg.define("_WIN32_WINNT", Some("0x8000"));
|
||||
}
|
||||
|
||||
fn headers(&self) -> Vec<&'static str> {
|
||||
let mut base = Vec::new();
|
||||
cfg.header("errno.h")
|
||||
.header("fcntl.h")
|
||||
.header("limits.h")
|
||||
.header("stddef.h")
|
||||
.header("stdint.h")
|
||||
.header("stdio.h")
|
||||
.header("stdlib.h")
|
||||
.header("sys/stat.h")
|
||||
.header("sys/types.h")
|
||||
.header("time.h")
|
||||
.header("wchar.h");
|
||||
|
||||
base.extend([
|
||||
"errno.h",
|
||||
"fcntl.h",
|
||||
"limits.h",
|
||||
"stddef.h",
|
||||
"stdint.h",
|
||||
"stdio.h",
|
||||
"stdlib.h",
|
||||
"sys/stat.h",
|
||||
"sys/types.h",
|
||||
"time.h",
|
||||
"wchar.h",
|
||||
].iter().cloned());
|
||||
if target.contains("apple-darwin") {
|
||||
cfg.header("mach-o/dyld.h");
|
||||
cfg.header("mach/mach_time.h");
|
||||
} else if target.contains("unknown-linux") ||
|
||||
target.contains("android") {
|
||||
cfg.header("linux/if_packet.h");
|
||||
cfg.header("net/ethernet.h");
|
||||
}
|
||||
|
||||
if self.target.contains("apple-darwin") {
|
||||
base.push("mach-o/dyld.h");
|
||||
base.push("mach/mach_time.h");
|
||||
if target.contains("windows") {
|
||||
cfg.header("winsock2.h"); // must be before windows.h
|
||||
|
||||
cfg.header("direct.h");
|
||||
cfg.header("io.h");
|
||||
cfg.header("sys/utime.h");
|
||||
cfg.header("windows.h");
|
||||
cfg.header("process.h");
|
||||
cfg.header("ws2ipdef.h");
|
||||
|
||||
if target.contains("gnu") {
|
||||
cfg.header("ws2tcpip.h");
|
||||
}
|
||||
} else {
|
||||
cfg.header("ctype.h");
|
||||
cfg.header("dirent.h");
|
||||
cfg.header("net/if.h");
|
||||
cfg.header("netdb.h");
|
||||
cfg.header("netinet/in.h");
|
||||
cfg.header("netinet/ip.h");
|
||||
cfg.header("netinet/tcp.h");
|
||||
cfg.header("pthread.h");
|
||||
cfg.header("signal.h");
|
||||
cfg.header("string.h");
|
||||
cfg.header("sys/file.h");
|
||||
cfg.header("sys/ioctl.h");
|
||||
cfg.header("sys/mman.h");
|
||||
cfg.header("sys/resource.h");
|
||||
cfg.header("sys/socket.h");
|
||||
cfg.header("sys/time.h");
|
||||
cfg.header("sys/un.h");
|
||||
cfg.header("sys/wait.h");
|
||||
cfg.header("unistd.h");
|
||||
cfg.header("utime.h");
|
||||
|
||||
if self.target.contains("unknown-linux") ||
|
||||
self.target.contains("android") {
|
||||
base.push("linux/if_packet.h");
|
||||
base.push("net/ethernet.h");
|
||||
}
|
||||
|
||||
if self.target.contains("windows") {
|
||||
base.push("winsock2.h"); // must be before windows.h
|
||||
|
||||
base.push("direct.h");
|
||||
base.push("io.h");
|
||||
base.push("sys/utime.h");
|
||||
base.push("windows.h");
|
||||
base.push("process.h");
|
||||
base.push("ws2ipdef.h");
|
||||
|
||||
if self.target.contains("gnu") {
|
||||
base.push("stdalign.h");
|
||||
base.push("ws2tcpip.h");
|
||||
}
|
||||
if target.contains("android") {
|
||||
cfg.header("arpa/inet.h");
|
||||
} else {
|
||||
base.push("ctype.h");
|
||||
base.push("dirent.h");
|
||||
base.push("net/if.h");
|
||||
base.push("netdb.h");
|
||||
base.push("netinet/in.h");
|
||||
base.push("netinet/ip.h");
|
||||
base.push("netinet/tcp.h");
|
||||
base.push("pthread.h");
|
||||
base.push("signal.h");
|
||||
base.push("string.h");
|
||||
base.push("sys/file.h");
|
||||
base.push("sys/ioctl.h");
|
||||
base.push("sys/mman.h");
|
||||
base.push("sys/resource.h");
|
||||
base.push("sys/socket.h");
|
||||
base.push("sys/time.h");
|
||||
base.push("sys/un.h");
|
||||
base.push("sys/wait.h");
|
||||
base.push("unistd.h");
|
||||
base.push("utime.h");
|
||||
|
||||
if self.target.contains("android") {
|
||||
base.push("arpa/inet.h");
|
||||
} else {
|
||||
base.push("glob.h");
|
||||
base.push("ifaddrs.h");
|
||||
if !self.target.contains("mips") {
|
||||
base.push("stdalign.h");
|
||||
}
|
||||
base.push("sys/sysctl.h");
|
||||
}
|
||||
cfg.header("glob.h");
|
||||
cfg.header("ifaddrs.h");
|
||||
cfg.header("sys/sysctl.h");
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
fn rust2c(&self, ty: &str) -> String {
|
||||
let windows = self.target.contains("windows");
|
||||
cfg.type_name(move |ty, is_struct| {
|
||||
match ty {
|
||||
t if t.starts_with("c_") => {
|
||||
match &ty[2..].replace("long", " long")[..] {
|
||||
s if s.starts_with("u") => format!("unsigned {}", &s[1..]),
|
||||
"short" => format!("short"),
|
||||
s if s.starts_with("s") => format!("signed {}", &s[1..]),
|
||||
s => s.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// Just pass all these through, no need for a "struct" prefix
|
||||
"glob_t" |
|
||||
"FILE" |
|
||||
@ -166,7 +92,7 @@ impl<'a> TestGenerator<'a> {
|
||||
// Windows uppercase structs don't have `struct` in front, there's a
|
||||
// few special cases for windows, and then otherwise put `struct` in
|
||||
// front of everything.
|
||||
t if self.structs.contains(t) => {
|
||||
t if is_struct => {
|
||||
if windows && ty.chars().next().unwrap().is_uppercase() {
|
||||
t.to_string()
|
||||
} else if windows && t == "stat" {
|
||||
@ -184,16 +110,17 @@ impl<'a> TestGenerator<'a> {
|
||||
|
||||
t => t.to_string(),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fn rust2cfield(&self, struct_: &str, field: &str) -> String {
|
||||
let target2 = target.clone();
|
||||
cfg.field_name(move |struct_, field| {
|
||||
match field {
|
||||
// Our stat *_nsec fields normally don't actually exist but are part
|
||||
// of a timeval struct
|
||||
s if s.ends_with("_nsec") && struct_ == "stat" => {
|
||||
if self.target.contains("apple-darwin") {
|
||||
if target2.contains("apple-darwin") {
|
||||
s.replace("_nsec", "spec.tv_nsec")
|
||||
} else if self.target.contains("android") {
|
||||
} else if target2.contains("android") {
|
||||
s.to_string()
|
||||
} else {
|
||||
s.replace("e_nsec", ".tv_nsec")
|
||||
@ -201,314 +128,49 @@ impl<'a> TestGenerator<'a> {
|
||||
}
|
||||
s => s.to_string(),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fn cfg_list(&self) -> Vec<(&'static str, Option<&'static str>)> {
|
||||
let mut ret = Vec::new();
|
||||
let (arch, target_pointer_width) = if self.target.starts_with("x86_64") {
|
||||
("x86_64", "64")
|
||||
} else if self.target.starts_with("i686") {
|
||||
("x86", "32")
|
||||
} else if self.target.starts_with("arm") {
|
||||
("arm", "32")
|
||||
} else if self.target.starts_with("mips") {
|
||||
("mips", "32")
|
||||
} else {
|
||||
panic!("unknown arch/pointer width: {}", self.target)
|
||||
};
|
||||
let (os, family, env) = if self.target.contains("unknown-linux-gnu") {
|
||||
("linux", "unix", "gnu")
|
||||
} else if self.target.contains("unknown-linux-musl") {
|
||||
("linux", "unix", "musl")
|
||||
} else if self.target.contains("apple-darwin") {
|
||||
("macos", "unix", "")
|
||||
} else if self.target.contains("windows-msvc") {
|
||||
("windows", "windows", "msvc")
|
||||
} else if self.target.contains("windows-gnu") {
|
||||
("windows", "windows", "gnu")
|
||||
} else if self.target.contains("android") {
|
||||
("android", "unix", "")
|
||||
} else if self.target.contains("unknown-freebsd") {
|
||||
("freebsd", "unix", "")
|
||||
} else {
|
||||
panic!("unknown os/family width: {}", self.target)
|
||||
};
|
||||
|
||||
ret.push((family, None));
|
||||
ret.push(("target_os", Some(os)));
|
||||
ret.push(("target_family", Some(family)));
|
||||
ret.push(("target_arch", Some(arch)));
|
||||
// skip endianness
|
||||
ret.push(("target_pointer_width", Some(target_pointer_width)));
|
||||
ret.push(("target_env", Some(env)));
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Prep the test generator
|
||||
let target = t!(env::var("TARGET"));
|
||||
let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
let rust_out = BufWriter::new(t!(File::create(out.join("all.rs"))));
|
||||
let c_out = BufWriter::new(t!(File::create(out.join("all.c"))));
|
||||
let sess = ParseSess::new();
|
||||
let mut tg = TestGenerator {
|
||||
target: target,
|
||||
rust: Box::new(rust_out),
|
||||
c: Box::new(c_out),
|
||||
sh: &sess.span_diagnostic,
|
||||
structs: HashSet::new(),
|
||||
abi: Abi::C,
|
||||
tests: Vec::new(),
|
||||
};
|
||||
|
||||
// Parse the libc crate
|
||||
let src = Path::new("../src/lib.rs");
|
||||
let cfg = Vec::new();
|
||||
let krate = parse::parse_crate_from_file(src, cfg, &sess);
|
||||
|
||||
// expand macros
|
||||
let ecfg = expand::ExpansionConfig::default("libc".to_string());
|
||||
let exts = vec![
|
||||
(intern("macro_rules"), SyntaxExtension::MacroRulesTT),
|
||||
];
|
||||
let mut krate = expand::expand_crate(&sess, ecfg, Vec::new(),
|
||||
exts, &mut Vec::new(), krate);
|
||||
|
||||
// Strip the crate down to just what's configured for our target
|
||||
for (k, v) in tg.cfg_list() {
|
||||
let s = InternedString::new;
|
||||
krate.config.push(match v {
|
||||
Some(v) => attr::mk_name_value_item_str(s(k), s(v)),
|
||||
None => attr::mk_word_item(s(k)),
|
||||
});
|
||||
}
|
||||
let mut gated_cfgs = Vec::new();
|
||||
let krate = syntax::config::strip_unconfigured_items(&sess.span_diagnostic,
|
||||
krate,
|
||||
&mut gated_cfgs);
|
||||
|
||||
// Probe the crate to find all structs (used to convert type names to names
|
||||
// in C).
|
||||
let mut structs = StructFinder {
|
||||
structs: HashSet::new(),
|
||||
};
|
||||
visit::walk_crate(&mut structs, &krate);
|
||||
tg.structs = structs.structs;
|
||||
|
||||
// Prep the C file by emitting header stuff
|
||||
for define in tg.defines() {
|
||||
t!(writeln!(tg.c, "#define {}", define));
|
||||
}
|
||||
for header in tg.headers() {
|
||||
t!(writeln!(tg.c, "#include <{}>", header));
|
||||
}
|
||||
|
||||
// Walk the crate, emitting test cases for everything found
|
||||
visit::walk_crate(&mut tg, &krate);
|
||||
tg.emit_run_all();
|
||||
|
||||
// Compile our C shim to be linked into tests
|
||||
let mut cfg = gcc::Config::new();
|
||||
cfg.file(out.join("all.c"));
|
||||
|
||||
if tg.target.contains("msvc") {
|
||||
cfg.flag("/W3").flag("/Wall").flag("/WX")
|
||||
.flag("/wd4820") // weird warning about adding padding?
|
||||
.flag("/wd4100") // don't warn about unused parameters
|
||||
.flag("/wd4996") // don't warn about deprecated functions
|
||||
.flag("/wd4296"); // don't warn about '<' being always false
|
||||
} else {
|
||||
cfg.flag("-Wall").flag("-Wextra").flag("-Werror")
|
||||
.flag("-Wno-unused-parameter")
|
||||
.flag("-Wno-type-limits");
|
||||
}
|
||||
|
||||
drop(tg);
|
||||
cfg.compile("liball.a");
|
||||
}
|
||||
|
||||
impl<'a> TestGenerator<'a> {
|
||||
fn test_type(&mut self, ty: &str) {
|
||||
let target2 = target.clone();
|
||||
cfg.skip_type(move |ty| {
|
||||
match ty {
|
||||
// sighandler_t is crazy across platforms
|
||||
"sighandler_t" => return,
|
||||
"sighandler_t" => true,
|
||||
|
||||
// Not actually defined on android, but it's not hurting anyone
|
||||
"in_port_t" if self.target.contains("android") => return,
|
||||
_ => {}
|
||||
"in_port_t" if target2.contains("android") => true,
|
||||
_ => false
|
||||
}
|
||||
let c = self.rust_ty_to_c_ty(ty);
|
||||
self.test_size_align(ty, &c);
|
||||
self.test_sign(ty, &c);
|
||||
}
|
||||
});
|
||||
|
||||
fn test_struct(&mut self, ty: &str, s: &ast::StructDef) {
|
||||
let cty = self.rust_ty_to_c_ty(ty);
|
||||
self.test_size_align(ty, &cty);
|
||||
|
||||
self.tests.push(format!("field_offset_size_{}", ty));
|
||||
t!(writeln!(self.rust, r#"
|
||||
fn field_offset_size_{ty}() {{
|
||||
println!("verifying struct {ty}");
|
||||
"#, ty = ty));
|
||||
for field in s.fields.iter() {
|
||||
let name = match field.node.kind {
|
||||
ast::NamedField(name, ast::Public) => name,
|
||||
ast::NamedField(_, ast::Inherited) => continue,
|
||||
ast::UnnamedField(..) => panic!("no tuple structs in FFI"),
|
||||
};
|
||||
|
||||
let cfield = self.rust2cfield(ty, &name.to_string());
|
||||
|
||||
t!(writeln!(self.c, r#"
|
||||
uint64_t __test_offset_{ty}_{rust_field}(void) {{
|
||||
return offsetof({cty}, {c_field});
|
||||
}}
|
||||
uint64_t __test_size_{ty}_{rust_field}(void) {{
|
||||
{cty}* foo = NULL;
|
||||
return sizeof(foo->{c_field});
|
||||
}}
|
||||
"#, ty = ty, cty = cty, rust_field = name, c_field = cfield));
|
||||
t!(writeln!(self.rust, r#"
|
||||
extern {{
|
||||
fn __test_offset_{ty}_{field}() -> u64;
|
||||
fn __test_size_{ty}_{field}() -> u64;
|
||||
}}
|
||||
unsafe {{
|
||||
let foo = 0 as *const {ty};
|
||||
same(offset_of!({ty}, {field}),
|
||||
__test_offset_{ty}_{field}(),
|
||||
"field offset {field} of {ty}");
|
||||
same(mem::size_of_val(&(*foo).{field}) as u64,
|
||||
__test_size_{ty}_{field}(),
|
||||
"field size {field} of {ty}");
|
||||
}}
|
||||
"#, ty = ty, field = name));
|
||||
}
|
||||
t!(writeln!(self.rust, r#"
|
||||
}}
|
||||
"#));
|
||||
}
|
||||
|
||||
fn test_size_align(&mut self, rust: &str, c: &str) {
|
||||
t!(writeln!(self.c, r#"
|
||||
uint64_t __test_size_{ty}(void) {{ return sizeof({cty}); }}
|
||||
uint64_t __test_align_{ty}(void) {{ return alignof({cty}); }}
|
||||
"#, ty = rust, cty = c));
|
||||
t!(writeln!(self.rust, r#"
|
||||
fn size_align_{ty}() {{
|
||||
extern {{
|
||||
fn __test_size_{ty}() -> u64;
|
||||
fn __test_align_{ty}() -> u64;
|
||||
}}
|
||||
println!("verifying type {ty} align/size");
|
||||
unsafe {{
|
||||
same(mem::size_of::<{ty}>() as u64,
|
||||
__test_size_{ty}(), "{ty} size");
|
||||
same(align::<{ty}>() as u64,
|
||||
__test_align_{ty}(), "{ty} align");
|
||||
}}
|
||||
}}
|
||||
"#, ty = rust));
|
||||
self.tests.push(format!("size_align_{}", rust));
|
||||
}
|
||||
|
||||
fn test_sign(&mut self, rust: &str, c: &str) {
|
||||
cfg.skip_signededness(|c| {
|
||||
match c {
|
||||
"LARGE_INTEGER" |
|
||||
"mach_timebase_info_data_t" |
|
||||
"float" |
|
||||
"double" => return,
|
||||
n if n.starts_with("pthread") => return,
|
||||
"double" => true,
|
||||
n if n.starts_with("pthread") => true,
|
||||
|
||||
// windows-isms
|
||||
n if n.starts_with("P") => return,
|
||||
n if n.starts_with("H") => return,
|
||||
n if n.starts_with("LP") => return,
|
||||
_ => {}
|
||||
n if n.starts_with("P") => true,
|
||||
n if n.starts_with("H") => true,
|
||||
n if n.starts_with("LP") => true,
|
||||
_ => false,
|
||||
}
|
||||
t!(writeln!(self.c, r#"
|
||||
uint32_t __test_signed_{ty}(void) {{
|
||||
return ((({cty}) -1) < 0);
|
||||
}}
|
||||
"#, ty = rust, cty = c));
|
||||
t!(writeln!(self.rust, r#"
|
||||
fn sign_{ty}() {{
|
||||
extern {{
|
||||
fn __test_signed_{ty}() -> u32;
|
||||
}}
|
||||
println!("verifying type {ty} sign");
|
||||
unsafe {{
|
||||
same(((!(0 as {ty})) < (0 as {ty})) as u32,
|
||||
__test_signed_{ty}(), "{ty} signed");
|
||||
}}
|
||||
}}
|
||||
"#, ty = rust));
|
||||
self.tests.push(format!("sign_{}", rust));
|
||||
}
|
||||
});
|
||||
|
||||
fn rust_ty_to_c_ty(&self, mut rust_ty: &str) -> String {
|
||||
let mut cty = self.rust2c(&rust_ty.replace("*mut ", "")
|
||||
.replace("*const ", ""));
|
||||
while rust_ty.starts_with("*") {
|
||||
if rust_ty.starts_with("*const") {
|
||||
cty = format!("const {}*", cty);
|
||||
rust_ty = &rust_ty[7..];
|
||||
} else {
|
||||
cty = format!("{}*", cty);
|
||||
rust_ty = &rust_ty[5..];
|
||||
}
|
||||
}
|
||||
return cty
|
||||
}
|
||||
|
||||
fn test_const(&mut self, name: &str, rust_ty: &str) {
|
||||
let mingw = self.target.contains("windows-gnu");
|
||||
|
||||
// Apparently these don't exist in mingw headers?
|
||||
// Apparently these don't exist in mingw headers?
|
||||
cfg.skip_const(move |name| {
|
||||
match name {
|
||||
"MEM_RESET_UNDO" |
|
||||
"FILE_ATTRIBUTE_NO_SCRUB_DATA" |
|
||||
"FILE_ATTRIBUTE_INTEGRITY_STREAM" |
|
||||
"ERROR_NOTHING_TO_TERMINATE" if mingw => return,
|
||||
_ => {}
|
||||
"ERROR_NOTHING_TO_TERMINATE" if mingw => true,
|
||||
"SIG_IGN" => true, // sighandler_t weirdness
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
let cty = self.rust_ty_to_c_ty(rust_ty);
|
||||
|
||||
// SIG_IGN has weird types on platforms, just worry about it as a size_t
|
||||
let cast = if name == "SIG_IGN" {"(size_t)"} else {""};
|
||||
|
||||
t!(writeln!(self.c, r#"
|
||||
int __test_const_{name}({cty} *outptr) {{
|
||||
*outptr = {cast}({name});
|
||||
return 1;
|
||||
}}
|
||||
"#, name = name, cast = cast, cty = cty));
|
||||
t!(writeln!(self.rust, r#"
|
||||
fn const_{name}() {{
|
||||
extern {{
|
||||
fn __test_const_{name}(out: *mut {ty}) -> c_int;
|
||||
}}
|
||||
println!("verifying const {name} value");
|
||||
unsafe {{
|
||||
let mut o = mem::zeroed();
|
||||
if __test_const_{name}(&mut o) == 0 {{
|
||||
panic!("{name} not defined");
|
||||
}} else {{
|
||||
same({name}, o, "{name} value");
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"#, ty = rust_ty, name = name));
|
||||
self.tests.push(format!("const_{}", name));
|
||||
}
|
||||
|
||||
fn test_extern_fn(&mut self, name: &str, cname: &str,
|
||||
args: &[String], ret: &str,
|
||||
variadic: bool, abi: Abi) {
|
||||
cfg.skip_fn(|name| {
|
||||
match name {
|
||||
// manually verified
|
||||
"execv" |
|
||||
@ -519,179 +181,10 @@ impl<'a> TestGenerator<'a> {
|
||||
"getrlimit" |
|
||||
"setrlimit" |
|
||||
"signal" |
|
||||
"getopt" => return,
|
||||
_ => {}
|
||||
"getopt" => true,
|
||||
_ => false,
|
||||
}
|
||||
let args = if args.len() == 0 && !variadic {
|
||||
"void".to_string()
|
||||
} else {
|
||||
args.iter().map(|a| self.rust_ty_to_c_ty(a)).collect::<Vec<_>>()
|
||||
.connect(", ") + if variadic {", ..."} else {""}
|
||||
};
|
||||
let cret = self.rust_ty_to_c_ty(ret);
|
||||
let abi = match abi {
|
||||
Abi::C => "",
|
||||
Abi::Stdcall => "__stdcall ",
|
||||
Abi::System if self.target.contains("i686-pc-windows") => {
|
||||
"__stdcall "
|
||||
}
|
||||
Abi::System => "",
|
||||
a => panic!("unknown ABI: {}", a),
|
||||
};
|
||||
t!(writeln!(self.c, r#"
|
||||
{ret} ({abi}*__test_fn_{name}(void))({args}) {{
|
||||
return {cname};
|
||||
}}
|
||||
"#, name = name, cname = cname, args = args, ret = cret, abi = abi));
|
||||
t!(writeln!(self.rust, r#"
|
||||
fn fn_{name}() {{
|
||||
extern {{
|
||||
fn __test_fn_{name}() -> size_t;
|
||||
}}
|
||||
println!("verifying function {name} pointer");
|
||||
unsafe {{
|
||||
same({name} as usize,
|
||||
__test_fn_{name}() as usize,
|
||||
"{name} function pointer");
|
||||
}}
|
||||
}}
|
||||
"#, name = name));
|
||||
self.tests.push(format!("fn_{}", name));
|
||||
}
|
||||
});
|
||||
|
||||
fn assert_no_generics(&self, _i: ast::Ident, generics: &ast::Generics) {
|
||||
assert!(generics.lifetimes.len() == 0);
|
||||
assert!(generics.ty_params.len() == 0);
|
||||
assert!(generics.where_clause.predicates.len() == 0);
|
||||
}
|
||||
|
||||
fn ty2name(&self, ty: &ast::Ty) -> String {
|
||||
match ty.node {
|
||||
ast::TyPath(_, ref path) => {
|
||||
path.segments.last().unwrap().identifier.to_string()
|
||||
}
|
||||
ast::TyPtr(ref t) => {
|
||||
format!("*{} {}", match t.mutbl {
|
||||
ast::MutImmutable => "const",
|
||||
ast::MutMutable => "mut",
|
||||
}, self.ty2name(&t.ty))
|
||||
}
|
||||
ast::TyBareFn(ref t) => {
|
||||
assert!(t.lifetimes.len() == 0);
|
||||
let (ret, mut args, variadic) = self.decl2rust(&t.decl);
|
||||
assert!(!variadic);
|
||||
if args.len() == 0 {
|
||||
args.push("void".to_string());
|
||||
}
|
||||
format!("{}(*)({})", ret, args.connect(", "))
|
||||
}
|
||||
_ => panic!("unknown ty {:?}", ty),
|
||||
}
|
||||
}
|
||||
|
||||
fn decl2rust(&self, decl: &ast::FnDecl) -> (String, Vec<String>, bool) {
|
||||
let args = decl.inputs.iter().map(|arg| {
|
||||
self.ty2name(&arg.ty)
|
||||
}).collect::<Vec<_>>();
|
||||
let ret = match decl.output {
|
||||
ast::NoReturn(..) |
|
||||
ast::DefaultReturn(..) => "void".to_string(),
|
||||
ast::Return(ref t) => self.ty2name(t),
|
||||
};
|
||||
(ret, args, decl.variadic)
|
||||
}
|
||||
|
||||
fn emit_run_all(&mut self) {
|
||||
t!(writeln!(self.rust, "
|
||||
fn run_all() {{
|
||||
"));
|
||||
for test in self.tests.iter() {
|
||||
if test.starts_with("fn_") {
|
||||
// FIXME: weird dllimport issues with windows?
|
||||
t!(writeln!(self.rust, "if cfg!(not(windows)) {{ {}(); }}",
|
||||
test));
|
||||
} else {
|
||||
t!(writeln!(self.rust, "{}();", test));
|
||||
}
|
||||
}
|
||||
t!(writeln!(self.rust, "
|
||||
}}
|
||||
"));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'v> Visitor<'v> for TestGenerator<'a> {
|
||||
fn visit_item(&mut self, i: &'v ast::Item) {
|
||||
let prev_abi = self.abi;
|
||||
match i.node {
|
||||
ast::ItemTy(_, ref generics) => {
|
||||
self.assert_no_generics(i.ident, generics);
|
||||
self.test_type(&i.ident.to_string());
|
||||
}
|
||||
|
||||
ast::ItemStruct(ref s, ref generics) => {
|
||||
self.assert_no_generics(i.ident, generics);
|
||||
let is_c = i.attrs.iter().any(|a| {
|
||||
attr::find_repr_attrs(self.sh, a).iter().any(|a| {
|
||||
*a == ReprAttr::ReprExtern
|
||||
})
|
||||
});
|
||||
if !is_c {
|
||||
panic!("{} is not marked #[repr(C)]", i.ident);
|
||||
}
|
||||
self.test_struct(&i.ident.to_string(), s);
|
||||
}
|
||||
|
||||
ast::ItemConst(ref ty, _) => {
|
||||
let ty = self.ty2name(ty);
|
||||
self.test_const(&i.ident.to_string(), &ty);
|
||||
}
|
||||
|
||||
ast::ItemForeignMod(ref fm) => {
|
||||
self.abi = fm.abi;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
visit::walk_item(self, i);
|
||||
self.abi = prev_abi;
|
||||
}
|
||||
|
||||
fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) {
|
||||
match i.node {
|
||||
ast::ForeignItemFn(ref decl, ref generics) => {
|
||||
self.assert_no_generics(i.ident, generics);
|
||||
let (ret, args, variadic) = self.decl2rust(decl);
|
||||
let cname = match attr::first_attr_value_str_by_name(&i.attrs,
|
||||
"link_name") {
|
||||
Some(ref i) if !i.to_string().contains("$") => {
|
||||
i.to_string()
|
||||
}
|
||||
_ => i.ident.to_string(),
|
||||
};
|
||||
let abi = self.abi;
|
||||
self.test_extern_fn(&i.ident.to_string(), &cname, &args, &ret,
|
||||
variadic, abi);
|
||||
}
|
||||
ast::ForeignItemStatic(_, _) => {
|
||||
}
|
||||
}
|
||||
visit::walk_foreign_item(self, i)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v> Visitor<'v> for StructFinder {
|
||||
fn visit_item(&mut self, i: &'v ast::Item) {
|
||||
match i.node {
|
||||
ast::ItemStruct(..) => {
|
||||
self.structs.insert(i.ident.to_string());
|
||||
}
|
||||
ast::ItemEnum(..) => {
|
||||
self.structs.insert(i.ident.to_string());
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
visit::walk_item(self, i)
|
||||
}
|
||||
cfg.generate("../src/lib.rs", "all.rs");
|
||||
}
|
||||
|
6
libc-test/src/main.rs
Normal file
6
libc-test/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#![allow(bad_style)]
|
||||
extern crate libc;
|
||||
|
||||
use libc::*;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/all.rs"));
|
@ -1,73 +0,0 @@
|
||||
#![allow(bad_style, unused_imports)]
|
||||
|
||||
extern crate libc;
|
||||
extern crate libc_test;
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::mem;
|
||||
|
||||
use libc::*;
|
||||
|
||||
trait Pretty {
|
||||
fn pretty(&self) -> String;
|
||||
}
|
||||
|
||||
impl<T> Pretty for *const T {
|
||||
fn pretty(&self) -> String { format!("{:?}", self) }
|
||||
}
|
||||
impl<T> Pretty for *mut T {
|
||||
fn pretty(&self) -> String { format!("{:?}", self) }
|
||||
}
|
||||
macro_rules! p {
|
||||
($($i:ident)*) => ($(
|
||||
impl Pretty for $i {
|
||||
fn pretty(&self) -> String { format!("{} ({:#x})", self, self) }
|
||||
}
|
||||
)*)
|
||||
}
|
||||
p! { i8 i16 i32 i64 u8 u16 u32 u64 usize isize }
|
||||
|
||||
static mut FAILED: bool = false;
|
||||
|
||||
fn same<T: Eq + Pretty>(rust: T, c: T, attr: &str) {
|
||||
if rust != c {
|
||||
println!("bad {}: rust: {} != c {}", attr, rust.pretty(), c.pretty());
|
||||
unsafe { FAILED = true; }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn align<T: Any>() -> u64 {
|
||||
// TODO: apparently these three types have less alignment in Rust on x86
|
||||
// than they do in C this difference should.. probably be reconciled.
|
||||
//
|
||||
// Perhaps #27195?
|
||||
if cfg!(target_pointer_width = "32") {
|
||||
if TypeId::of::<T>() == TypeId::of::<f64>() ||
|
||||
TypeId::of::<T>() == TypeId::of::<i64>() ||
|
||||
TypeId::of::<T>() == TypeId::of::<u64>() {
|
||||
return 8
|
||||
}
|
||||
}
|
||||
mem::min_align_of::<T>() as u64
|
||||
}
|
||||
|
||||
macro_rules! offset_of {
|
||||
($ty:ident, $field:ident) => (
|
||||
(&((*(0 as *const $ty)).$field)) as *const _ as u64
|
||||
)
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/all.rs"));
|
||||
|
||||
fn main() {
|
||||
println!("RUNNING ALL TESTS");
|
||||
run_all();
|
||||
unsafe {
|
||||
if FAILED {
|
||||
panic!("some tests failed");
|
||||
} else {
|
||||
println!("PASSED");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user