Switch to ctest crate

Extracted tests!
diff --git a/appveyor.yml b/appveyor.yml
index 7c01515..cdd5bf1 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -25,4 +25,4 @@
 
 test_script:
   - cargo test
-  - cargo test --manifest-path libc-test/Cargo.toml
+  - cargo run --manifest-path libc-test/Cargo.toml
diff --git a/ci/run.sh b/ci/run.sh
index 368037b..9604f1b 100644
--- a/ci/run.sh
+++ b/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
diff --git a/libc-test/Cargo.lock b/libc-test/Cargo.lock
deleted file mode 100644
index 1726c53..0000000
--- a/libc-test/Cargo.lock
+++ /dev/null
@@ -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"
-
diff --git a/libc-test/Cargo.toml b/libc-test/Cargo.toml
index fee56fe..9ff4765 100644
--- a/libc-test/Cargo.toml
+++ b/libc-test/Cargo.toml
@@ -8,14 +8,4 @@
 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" }
diff --git a/libc-test/build.rs b/libc-test/build.rs
index 7bca148..50ded54 100644
--- a/libc-test/build.rs
+++ b/libc-test/build.rs
@@ -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 @@
             // 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 @@
 
             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 @@
             }
             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 @@
             "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");
 }
diff --git a/libc-test/src/lib.rs b/libc-test/src/lib.rs
deleted file mode 100644
index e69de29..0000000
--- a/libc-test/src/lib.rs
+++ /dev/null
diff --git a/libc-test/src/main.rs b/libc-test/src/main.rs
new file mode 100644
index 0000000..e4cb8fe
--- /dev/null
+++ b/libc-test/src/main.rs
@@ -0,0 +1,6 @@
+#![allow(bad_style)]
+extern crate libc;
+
+use libc::*;
+
+include!(concat!(env!("OUT_DIR"), "/all.rs"));
diff --git a/libc-test/tests/all.rs b/libc-test/tests/all.rs
deleted file mode 100644
index a2f4daf..0000000
--- a/libc-test/tests/all.rs
+++ /dev/null
@@ -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");
-        }
-    }
-}