<NME> tests.rs
<BEF> extern crate libedgedns;
extern crate nix;
extern crate regex;


#[cfg(test)]
mod test {
    extern crate env_logger;
    use libedgedns::{Config, EdgeDNS};
    use std::env;
    use std::os::unix::io::RawFd;
    use nix::sys::signal::{kill, SIGKILL};
    use nix::sys::ioctl::libc::pid_t;
    use nix::unistd::*;
    use nix::sys::socket::{socketpair, AddressFamily, SockType, SockFlag};
    use regex::Regex;
    use std::process::{Output, Command, ExitStatus};
    use std::string::String;

    struct EdgeDNSInstance {
        stdout: RawFd,
        pid: pid_t,
        udp_ports: Vec<u16>,
    }
    impl EdgeDNSInstance {
        pub fn done(&self) {
            kill(self.pid, SIGKILL).expect("kill failed")
        }
    }
    fn spawn_edgedns(cfg_str: &str) -> EdgeDNSInstance {
        let mut ret = EdgeDNSInstance {
            pid: 0,
            stdout: 0,
            udp_ports: Vec::new(),
        };
        let (fdc, fdp) = socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty())
            .expect("socketpair");
        match fork().expect("fork failed") {
            ForkResult::Parent { child } => {
                ret.pid = child;
                ret.stdout = fdp;
                let mut s = String::new();
                loop {
                    let mut buf = [0; 1];
                    let res = read(fdp, &mut buf);
                    alarm(timeout.as_secs() as u32);
                }
                    }
                    let res = res.unwrap();
                    if res > 0 {
                        s.push(buf[0] as char);

                    }
                    if s.contains("UDP listener is ready") && s.contains("TCP listener is ready") {
                        break;
                    }
                }
                let re = Regex::new(r"Created a UDP socket: (\d+)").unwrap();
                for cap in re.captures_iter(&s) {
                    ret.udp_ports.push(cap[1].parse::<u16>().unwrap());
                }
            }
            ForkResult::Child => {
                env::set_var("RUST_LOG", "info");
                dup2(fdc, 2).expect("dup2 failed"); /* have env_logger log to the socketpair */
                env_logger::init().expect("Failed to init logger");
                let config = Config::from_string(cfg_str);
                assert!(config.is_ok());
                EdgeDNS::new(config.unwrap());
            }
        }
        ret
    }
    enum Qprotocol {
        UDP,
        TCP,
                let re = Regex::new(r"0t0\s+UDP \*:(\d+)").unwrap();
                let mut udp_port: u16 = 0;
                for cap in re.captures_iter(&lsof) {
                    udp_port = cap[1].parse::<u16>().unwrap();
        status: ExitStatus,
    }
    fn dig(query: &str, proto: Qprotocol, server: &str, port: u16) -> CmdOutput {
        let output = Command::new("dig")
            .arg(query)
            .arg(format!("@{}", server))
            .arg("-p")
            .arg(format!("{}", port))
            .output()
            .unwrap();
        let mut stdout = String::new();
        let mut stderr = String::new();
        match String::from_utf8(output.stdout) {
        stdout: String,
        #[allow(dead_code)]
        stderr: String,
        #[allow(dead_code)]
        status: ExitStatus,
    }
    fn dig(query: &str, proto: Qprotocol, server: &str, port: u16) -> CmdOutput {
        let server = format!("@{}", server);
        let port = format!("{}", port);
        let mut args = vec![query, &server, "-p", &port];
        match proto {
            Qprotocol::TCP => args.push("+tcp"),
    }

    /* tests */

    #[test]
    fn empty_config() {
listen = "127.0.0.1:0"
"#,
            coredns.udp_port
        );
        let server = spawn_edgedns(&cfg);
        let re = Regex::new(
            r"\n;; ANSWER SECTION:\nmail.example.com.\s+\d+\s+IN\s+A\s+192.0.2.3",
        ).unwrap();
threads_tcp = 1
"#;
        let server = spawn_edgedns(&cfg);
        server.done();
    }

    #[test]
            println!("{}", output);
            assert!(re.is_match(&output));
        }
    }

    #[test]
    fn empty_config() {
        let cfg = r#"
[upstream]
servers = ["127.0.0.1:53"]
[network]
listen = "127.0.0.1:0"
        let re = Regex::new(r"\n;; ANSWER SECTION:\n127.0.0.1.xip.io.\s+\d+\s+IN\s+A\s+127.0.0.1")
            .unwrap();
        for port in &server.udp_ports {
            assert!(re.is_match(&dig("127.0.0.1.xip.io", Qprotocol::UDP, "127.0.0.1", *port).stdout));
        }
        server.done();
    }
}

<MSG> Add a function that uses coredns as a test backend

<DFF> @@ -1,45 +1,77 @@
 extern crate libedgedns;
 extern crate nix;
 extern crate regex;
-
+extern crate tempfile;
 
 #[cfg(test)]
 mod test {
     extern crate env_logger;
     use libedgedns::{Config, EdgeDNS};
-    use std::env;
-    use std::os::unix::io::RawFd;
+
     use nix::sys::signal::{kill, SIGKILL};
     use nix::sys::ioctl::libc::pid_t;
-    use nix::unistd::*;
+    use nix::unistd::{fork, read, ForkResult, dup2};
+    use nix::sys::ioctl::libc::alarm;
     use nix::sys::socket::{socketpair, AddressFamily, SockType, SockFlag};
+
     use regex::Regex;
-    use std::process::{Output, Command, ExitStatus};
+
+    use std::env;
+    use std::io::Write;
+    use std::process::{exit, Output, Command, ExitStatus};
+    use std::os::unix::io::RawFd;
+    use std::os::unix::process::CommandExt;
     use std::string::String;
+    use std::time::Duration;
+
+    use tempfile::NamedTempFile;
 
     struct EdgeDNSInstance {
-        stdout: RawFd,
-        pid: pid_t,
+        server: Server,
         udp_ports: Vec<u16>,
+        tcp_ports: Vec<u16>,
+    }
+
+    struct Server {
+        pid: pid_t,
+        startup_text: String,
+        output: RawFd,
+    }
+    impl Server {
+            pub fn new() -> Server {
+                Server{pid: 0, startup_text: String::from(""), output: 0}
+            }
     }
-    impl EdgeDNSInstance {
-        pub fn done(&self) {
-            kill(self.pid, SIGKILL).expect("kill failed")
+
+    impl Drop for Server {
+        fn drop(&mut self) {
+            if self.pid != 0 {
+                kill(self.pid, SIGKILL).expect("kill failed")
+            }
         }
     }
-    fn spawn_edgedns(cfg_str: &str) -> EdgeDNSInstance {
-        let mut ret = EdgeDNSInstance {
+
+    fn spawn_server<F1, F2>(child_fn: F1, mut is_ready: F2, timeout: Duration) -> Server
+        where F1: Fn() -> (),
+              F2: FnMut(&str, pid_t) -> bool
+    {
+
+        let mut server = Server {
             pid: 0,
-            stdout: 0,
-            udp_ports: Vec::new(),
+            output: 0,
+            startup_text: String::new(),
         };
         let (fdc, fdp) = socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty())
             .expect("socketpair");
         match fork().expect("fork failed") {
             ForkResult::Parent { child } => {
-                ret.pid = child;
-                ret.stdout = fdp;
-                let mut s = String::new();
+                server.pid = child;
+                server.output = fdp;
+
+                /* FIXME: a non-unsafe way to do this */
+                unsafe {
+                    alarm(timeout.as_secs() as u32);
+                }
                 loop {
                     let mut buf = [0; 1];
                     let res = read(fdp, &mut buf);
@@ -48,29 +80,111 @@ mod test {
                     }
                     let res = res.unwrap();
                     if res > 0 {
-                        s.push(buf[0] as char);
-
+                        server.startup_text.push(buf[0] as char);
                     }
-                    if s.contains("UDP listener is ready") && s.contains("TCP listener is ready") {
+                    if is_ready(&server.startup_text, child) {
                         break;
                     }
                 }
-                let re = Regex::new(r"Created a UDP socket: (\d+)").unwrap();
-                for cap in re.captures_iter(&s) {
-                    ret.udp_ports.push(cap[1].parse::<u16>().unwrap());
+                unsafe {
+                    alarm(0);
                 }
             }
             ForkResult::Child => {
-                env::set_var("RUST_LOG", "info");
-                dup2(fdc, 2).expect("dup2 failed"); /* have env_logger log to the socketpair */
-                env_logger::init().expect("Failed to init logger");
-                let config = Config::from_string(cfg_str);
-                assert!(config.is_ok());
-                EdgeDNS::new(config.unwrap());
+                dup2(fdc, 1).expect("dup2 failed");
+                dup2(fdc, 2).expect("dup2 failed");
+                child_fn();
+                println!("The child exited");
+                exit(0);
+            }
+        }
+        server
+    }
+
+    fn spawn_edgedns(cfg_str: &str) -> EdgeDNSInstance {
+        let mut ret = EdgeDNSInstance {
+            udp_ports: Vec::new(),
+            tcp_ports: Vec::new(),
+            server: spawn_server(|| {
+                                     env::set_var("RUST_LOG", "info");
+                                     env_logger::init().expect("Failed to init logger");
+                                     let config = Config::from_string(cfg_str);
+                                     assert!(config.is_ok());
+                                     EdgeDNS::new(config.unwrap());
+                                 },
+                                 |out, _| {
+                                     out.contains("UDP listener is ready") &&
+                                     out.contains("TCP listener is ready")
+                                 },
+                                 Duration::new(5, 0)),
+        };
+
+        for proto in ["TCP", "UDP"].iter() {
+            let re = Regex::new(&format!(r"Created a {} socket: (\d+)", proto)).unwrap();
+            for cap in re.captures_iter(&ret.server.startup_text) {
+                let port = cap[1].parse::<u16>().unwrap();
+                if *proto == "UDP" {
+                    ret.udp_ports.push(port);
+                } else {
+                    ret.tcp_ports.push(port);
+                }
             }
         }
+
+        ret
+    }
+
+    struct CoreDNS {
+        server: Server,
+        udp_port: u16,
+    }
+
+    fn spawn_coredns(domain: &str, zone_str: &str) -> CoreDNS {
+        let mut conf_file = NamedTempFile::new().unwrap();
+        let mut zone_file = NamedTempFile::new().unwrap();
+        zone_file.write_all(zone_str.as_bytes());
+        let zfile_path = zone_file.path().to_str().unwrap();
+        let conf_str = format!(r#"
+{}:0 {{
+    file {}
+    errors stdout
+    log stdout
+}}
+"#, domain, zfile_path);
+        conf_file.write_all(conf_str.as_bytes());
+        let cfile_path = conf_file.path().to_str().unwrap();
+        let mut ret = CoreDNS {
+            udp_port : 0,
+            server: Server::new(),
+        };
+        ret.server = spawn_server(|| {
+                let cmd = Command::new("coredns")
+                    .args(&["-log", "-dns.port", "0", "-conf", cfile_path])
+                    .exec();
+                ::std::process::exit(1);
+            },
+            |out, pid| {
+                if !out.contains("CoreDNS-008") {
+                    return false;
+                }
+                let lsof = Command::new("lsof")
+                    .arg("-p")
+                    .arg(&format!("{}", pid))
+                    .output();
+                let lsof = String::from_utf8(lsof.unwrap().stdout).unwrap();
+
+                let re = Regex::new(r"0t0\s+UDP \*:(\d+)").unwrap();
+                let mut udp_port: u16 = 0;
+                for cap in re.captures_iter(&lsof) {
+                    udp_port = cap[1].parse::<u16>().unwrap();
+                }
+                ret.udp_port = udp_port;
+                true
+            },
+            Duration::new(5, 0));
         ret
     }
+
     enum Qprotocol {
         UDP,
         TCP,
@@ -81,13 +195,14 @@ mod test {
         status: ExitStatus,
     }
     fn dig(query: &str, proto: Qprotocol, server: &str, port: u16) -> CmdOutput {
-        let output = Command::new("dig")
-            .arg(query)
-            .arg(format!("@{}", server))
-            .arg("-p")
-            .arg(format!("{}", port))
-            .output()
-            .unwrap();
+        let server = format!("@{}", server);
+        let port = format!("{}", port);
+        let mut args = vec![query, &server, "-p", &port];
+        match proto {
+            Qprotocol::TCP => args.push("+tcp"),
+            _ => (),
+        }
+        let output = Command::new("dig").args(args).output().unwrap();
         let mut stdout = String::new();
         let mut stderr = String::new();
         match String::from_utf8(output.stdout) {
@@ -106,6 +221,49 @@ mod test {
     }
 
     /* tests */
+    #[test]
+    fn coredns_test() {
+        let coredns = spawn_coredns("example.com", r#"
+$ORIGIN example.com.     ; designates the start of this zone file in the namespace
+$TTL 1h                  ; default expiration time of all resource records without their own TTL value
+example.com.  IN  SOA   ns.example.com. username.example.com. ( 2007120710 1d 2h 4w 1h )
+example.com.  IN  NS    ns                    ; ns.example.com is a nameserver for example.com
+example.com.  IN  NS    ns.somewhere.example. ; ns.somewhere.example is a backup nameserver for example.com
+example.com.  IN  MX    10 mail.example.com.  ; mail.example.com is the mailserver for example.com
+@             IN  MX    20 mail2.example.com. ; equivalent to above line, "@" represents zone origin
+@             IN  MX    50 mail3              ; equivalent to above line, but using a relative host name
+example.com.  IN  A     192.0.2.1             ; IPv4 address for example.com
+IN  AAAA  2001:db8:10::1        ; IPv6 address for example.com
+ns            IN  A     192.0.2.2             ; IPv4 address for ns.example.com
+IN  AAAA  2001:db8:10::2        ; IPv6 address for ns.example.com
+www           IN  CNAME example.com.          ; www.example.com is an alias for example.com
+wwwtest       IN  CNAME www                   ; wwwtest.example.com is another alias for www.example.com
+mail          IN  A     192.0.2.3             ; IPv4 address for mail.example.com
+mail2         IN  A     192.0.2.4             ; IPv4 address for mail2.example.com
+mail3         IN  A     192.0.2.5             ; IPv4 address for mail3.example.com
+"#);
+        let cfg = format!(r#"
+[upstream]
+servers = ["127.0.0.1:{}"]
+[network]
+listen = "127.0.0.1:0"
+udp_ports = 1
+[global]
+threads_udp = 1
+threads_tcp = 1
+"#, coredns.udp_port);
+        let server = spawn_edgedns(&cfg);
+        let re = Regex::new(r"\n;; ANSWER SECTION:\nmail.example.com.\s+\d+\s+IN\s+A\s+192.0.2.3").unwrap();
+        for port in &server.udp_ports {
+            let output = dig("mail.example.com", Qprotocol::UDP, "127.0.0.1", *port).stdout;
+            assert!(re.is_match(&output));
+        }
+        for port in &server.tcp_ports {
+            let output = dig("127.0.0.1.xip.io", Qprotocol::TCP, "127.0.0.1", *port).stdout;
+            println!("{}", output);
+            assert!(re.is_match(&output));
+        }
+    }
 
     #[test]
     fn empty_config() {
@@ -120,7 +278,6 @@ threads_udp = 1
 threads_tcp = 1
 "#;
         let server = spawn_edgedns(&cfg);
-        server.done();
     }
 
     #[test]
@@ -139,8 +296,12 @@ threads_tcp = 1
         let re = Regex::new(r"\n;; ANSWER SECTION:\n127.0.0.1.xip.io.\s+\d+\s+IN\s+A\s+127.0.0.1")
             .unwrap();
         for port in &server.udp_ports {
-            assert!(re.is_match(&dig("127.0.0.1.xip.io", Qprotocol::UDP, "127.0.0.1", *port).stdout));
+            let output = dig("127.0.0.1.xip.io", Qprotocol::UDP, "127.0.0.1", *port).stdout;
+            assert!(re.is_match(&output));
+        }
+        for port in &server.tcp_ports {
+            let output = dig("127.0.0.1.xip.io", Qprotocol::TCP, "127.0.0.1", *port).stdout;
+            assert!(re.is_match(&output));
         }
-        server.done();
     }
 }

{'commit': 'diff --git a/Cargo.lock b/Cargo.lock\nindex cecfdb2..c615c19 100644\n--- a/Cargo.lock\n+++ b/Cargo.lock\n@@ -8,6 +8,7 @@ dependencies = [\n  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",\n  "nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",\n  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",\n+ "tempfile 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)",\n ]\n \n [[package]]\n@@ -656,6 +657,18 @@ name = "take"\n version = "0.1.0"\n source = "registry+https://github.com/rust-lang/crates.io-index"\n \n+[[package]]\n+name = "tempfile"\n+version = "2.1.5"\n+source = "registry+https://github.com/rust-lang/crates.io-index"\n+dependencies = [\n+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",\n+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",\n+ "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",\n+ "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",\n+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",\n+]\n+\n [[package]]\n name = "term_size"\n version = "0.3.0"\n@@ -912,6 +925,7 @@ dependencies = [\n "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"\n "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"\n "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"\n+"checksum tempfile 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3213fd2b7ed87e39306737ccfac04b1233b57a33ca64cfbf52f2ffaa2b765e2f"\n "checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"\n "checksum thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8df7875b676fddfadffd96deea3b1124e5ede707d4884248931077518cf1f773"\n "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7"\ndiff --git a/Cargo.toml b/Cargo.toml\nindex 1b21d94..9ecedfb 100644\n--- a/Cargo.toml\n+++ b/Cargo.toml\n@@ -13,6 +13,7 @@ default = ["nightly", "webservice"]\n [dev-dependencies]\n nix = "*"\n regex = "*"\n+tempfile = "*"\n \n [dependencies.libedgedns]\n path = "src/libedgedns"\ndiff --git a/tests/tests.rs b/tests/tests.rs\nindex 4637d7a..da525db 100644\n--- a/tests/tests.rs\n+++ b/tests/tests.rs\n@@ -1,45 +1,77 @@\n extern crate libedgedns;\n extern crate nix;\n extern crate regex;\n-\n+extern crate tempfile;\n \n #[cfg(test)]\n mod test {\n     extern crate env_logger;\n     use libedgedns::{Config, EdgeDNS};\n-    use std::env;\n-    use std::os::unix::io::RawFd;\n+\n     use nix::sys::signal::{kill, SIGKILL};\n     use nix::sys::ioctl::libc::pid_t;\n-    use nix::unistd::*;\n+    use nix::unistd::{fork, read, ForkResult, dup2};\n+    use nix::sys::ioctl::libc::alarm;\n     use nix::sys::socket::{socketpair, AddressFamily, SockType, SockFlag};\n+\n     use regex::Regex;\n-    use std::process::{Output, Command, ExitStatus};\n+\n+    use std::env;\n+    use std::io::Write;\n+    use std::process::{exit, Output, Command, ExitStatus};\n+    use std::os::unix::io::RawFd;\n+    use std::os::unix::process::CommandExt;\n     use std::string::String;\n+    use std::time::Duration;\n+\n+    use tempfile::NamedTempFile;\n \n     struct EdgeDNSInstance {\n-        stdout: RawFd,\n-        pid: pid_t,\n+        server: Server,\n         udp_ports: Vec<u16>,\n+        tcp_ports: Vec<u16>,\n+    }\n+\n+    struct Server {\n+        pid: pid_t,\n+        startup_text: String,\n+        output: RawFd,\n+    }\n+    impl Server {\n+            pub fn new() -> Server {\n+                Server{pid: 0, startup_text: String::from(""), output: 0}\n+            }\n     }\n-    impl EdgeDNSInstance {\n-        pub fn done(&self) {\n-            kill(self.pid, SIGKILL).expect("kill failed")\n+\n+    impl Drop for Server {\n+        fn drop(&mut self) {\n+            if self.pid != 0 {\n+                kill(self.pid, SIGKILL).expect("kill failed")\n+            }\n         }\n     }\n-    fn spawn_edgedns(cfg_str: &str) -> EdgeDNSInstance {\n-        let mut ret = EdgeDNSInstance {\n+\n+    fn spawn_server<F1, F2>(child_fn: F1, mut is_ready: F2, timeout: Duration) -> Server\n+        where F1: Fn() -> (),\n+              F2: FnMut(&str, pid_t) -> bool\n+    {\n+\n+        let mut server = Server {\n             pid: 0,\n-            stdout: 0,\n-            udp_ports: Vec::new(),\n+            output: 0,\n+            startup_text: String::new(),\n         };\n         let (fdc, fdp) = socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty())\n             .expect("socketpair");\n         match fork().expect("fork failed") {\n             ForkResult::Parent { child } => {\n-                ret.pid = child;\n-                ret.stdout = fdp;\n-                let mut s = String::new();\n+                server.pid = child;\n+                server.output = fdp;\n+\n+                /* FIXME: a non-unsafe way to do this */\n+                unsafe {\n+                    alarm(timeout.as_secs() as u32);\n+                }\n                 loop {\n                     let mut buf = [0; 1];\n                     let res = read(fdp, &mut buf);\n@@ -48,29 +80,111 @@ mod test {\n                     }\n                     let res = res.unwrap();\n                     if res > 0 {\n-                        s.push(buf[0] as char);\n-\n+                        server.startup_text.push(buf[0] as char);\n                     }\n-                    if s.contains("UDP listener is ready") && s.contains("TCP listener is ready") {\n+                    if is_ready(&server.startup_text, child) {\n                         break;\n                     }\n                 }\n-                let re = Regex::new(r"Created a UDP socket: (\\d+)").unwrap();\n-                for cap in re.captures_iter(&s) {\n-                    ret.udp_ports.push(cap[1].parse::<u16>().unwrap());\n+                unsafe {\n+                    alarm(0);\n                 }\n             }\n             ForkResult::Child => {\n-                env::set_var("RUST_LOG", "info");\n-                dup2(fdc, 2).expect("dup2 failed"); /* have env_logger log to the socketpair */\n-                env_logger::init().expect("Failed to init logger");\n-                let config = Config::from_string(cfg_str);\n-                assert!(config.is_ok());\n-                EdgeDNS::new(config.unwrap());\n+                dup2(fdc, 1).expect("dup2 failed");\n+                dup2(fdc, 2).expect("dup2 failed");\n+                child_fn();\n+                println!("The child exited");\n+                exit(0);\n+            }\n+        }\n+        server\n+    }\n+\n+    fn spawn_edgedns(cfg_str: &str) -> EdgeDNSInstance {\n+        let mut ret = EdgeDNSInstance {\n+            udp_ports: Vec::new(),\n+            tcp_ports: Vec::new(),\n+            server: spawn_server(|| {\n+                                     env::set_var("RUST_LOG", "info");\n+                                     env_logger::init().expect("Failed to init logger");\n+                                     let config = Config::from_string(cfg_str);\n+                                     assert!(config.is_ok());\n+                                     EdgeDNS::new(config.unwrap());\n+                                 },\n+                                 |out, _| {\n+                                     out.contains("UDP listener is ready") &&\n+                                     out.contains("TCP listener is ready")\n+                                 },\n+                                 Duration::new(5, 0)),\n+        };\n+\n+        for proto in ["TCP", "UDP"].iter() {\n+            let re = Regex::new(&format!(r"Created a {} socket: (\\d+)", proto)).unwrap();\n+            for cap in re.captures_iter(&ret.server.startup_text) {\n+                let port = cap[1].parse::<u16>().unwrap();\n+                if *proto == "UDP" {\n+                    ret.udp_ports.push(port);\n+                } else {\n+                    ret.tcp_ports.push(port);\n+                }\n             }\n         }\n+\n+        ret\n+    }\n+\n+    struct CoreDNS {\n+        server: Server,\n+        udp_port: u16,\n+    }\n+\n+    fn spawn_coredns(domain: &str, zone_str: &str) -> CoreDNS {\n+        let mut conf_file = NamedTempFile::new().unwrap();\n+        let mut zone_file = NamedTempFile::new().unwrap();\n+        zone_file.write_all(zone_str.as_bytes());\n+        let zfile_path = zone_file.path().to_str().unwrap();\n+        let conf_str = format!(r#"\n+{}:0 {{\n+    file {}\n+    errors stdout\n+    log stdout\n+}}\n+"#, domain, zfile_path);\n+        conf_file.write_all(conf_str.as_bytes());\n+        let cfile_path = conf_file.path().to_str().unwrap();\n+        let mut ret = CoreDNS {\n+            udp_port : 0,\n+            server: Server::new(),\n+        };\n+        ret.server = spawn_server(|| {\n+                let cmd = Command::new("coredns")\n+                    .args(&["-log", "-dns.port", "0", "-conf", cfile_path])\n+                    .exec();\n+                ::std::process::exit(1);\n+            },\n+            |out, pid| {\n+                if !out.contains("CoreDNS-008") {\n+                    return false;\n+                }\n+                let lsof = Command::new("lsof")\n+                    .arg("-p")\n+                    .arg(&format!("{}", pid))\n+                    .output();\n+                let lsof = String::from_utf8(lsof.unwrap().stdout).unwrap();\n+\n+                let re = Regex::new(r"0t0\\s+UDP \\*:(\\d+)").unwrap();\n+                let mut udp_port: u16 = 0;\n+                for cap in re.captures_iter(&lsof) {\n+                    udp_port = cap[1].parse::<u16>().unwrap();\n+                }\n+                ret.udp_port = udp_port;\n+                true\n+            },\n+            Duration::new(5, 0));\n         ret\n     }\n+\n     enum Qprotocol {\n         UDP,\n         TCP,\n@@ -81,13 +195,14 @@ mod test {\n         status: ExitStatus,\n     }\n     fn dig(query: &str, proto: Qprotocol, server: &str, port: u16) -> CmdOutput {\n-        let output = Command::new("dig")\n-            .arg(query)\n-            .arg(format!("@{}", server))\n-            .arg("-p")\n-            .arg(format!("{}", port))\n-            .output()\n-            .unwrap();\n+        let server = format!("@{}", server);\n+        let port = format!("{}", port);\n+        let mut args = vec![query, &server, "-p", &port];\n+        match proto {\n+            Qprotocol::TCP => args.push("+tcp"),\n+            _ => (),\n+        }\n+        let output = Command::new("dig").args(args).output().unwrap();\n         let mut stdout = String::new();\n         let mut stderr = String::new();\n         match String::from_utf8(output.stdout) {\n@@ -106,6 +221,49 @@ mod test {\n     }\n \n     /* tests */\n+    #[test]\n+    fn coredns_test() {\n+        let coredns = spawn_coredns("example.com", r#"\n+$ORIGIN example.com.     ; designates the start of this zone file in the namespace\n+$TTL 1h                  ; default expiration time of all resource records without their own TTL value\n+example.com.  IN  SOA   ns.example.com. username.example.com. ( 2007120710 1d 2h 4w 1h )\n+example.com.  IN  NS    ns                    ; ns.example.com is a nameserver for example.com\n+example.com.  IN  NS    ns.somewhere.example. ; ns.somewhere.example is a backup nameserver for example.com\n+example.com.  IN  MX    10 mail.example.com.  ; mail.example.com is the mailserver for example.com\n+@             IN  MX    20 mail2.example.com. ; equivalent to above line, "@" represents zone origin\n+@             IN  MX    50 mail3              ; equivalent to above line, but using a relative host name\n+example.com.  IN  A     192.0.2.1             ; IPv4 address for example.com\n+IN  AAAA  2001:db8:10::1        ; IPv6 address for example.com\n+ns            IN  A     192.0.2.2             ; IPv4 address for ns.example.com\n+IN  AAAA  2001:db8:10::2        ; IPv6 address for ns.example.com\n+www           IN  CNAME example.com.          ; www.example.com is an alias for example.com\n+wwwtest       IN  CNAME www                   ; wwwtest.example.com is another alias for www.example.com\n+mail          IN  A     192.0.2.3             ; IPv4 address for mail.example.com\n+mail2         IN  A     192.0.2.4             ; IPv4 address for mail2.example.com\n+mail3         IN  A     192.0.2.5             ; IPv4 address for mail3.example.com\n+"#);\n+        let cfg = format!(r#"\n+[upstream]\n+servers = ["127.0.0.1:{}"]\n+[network]\n+listen = "127.0.0.1:0"\n+udp_ports = 1\n+[global]\n+threads_udp = 1\n+threads_tcp = 1\n+"#, coredns.udp_port);\n+        let server = spawn_edgedns(&cfg);\n+        let re = Regex::new(r"\\n;; ANSWER SECTION:\\nmail.example.com.\\s+\\d+\\s+IN\\s+A\\s+192.0.2.3").unwrap();\n+        for port in &server.udp_ports {\n+            let output = dig("mail.example.com", Qprotocol::UDP, "127.0.0.1", *port).stdout;\n+            assert!(re.is_match(&output));\n+        }\n+        for port in &server.tcp_ports {\n+            let output = dig("127.0.0.1.xip.io", Qprotocol::TCP, "127.0.0.1", *port).stdout;\n+            println!("{}", output);\n+            assert!(re.is_match(&output));\n+        }\n+    }\n \n     #[test]\n     fn empty_config() {\n@@ -120,7 +278,6 @@ threads_udp = 1\n threads_tcp = 1\n "#;\n         let server = spawn_edgedns(&cfg);\n-        server.done();\n     }\n \n     #[test]\n@@ -139,8 +296,12 @@ threads_tcp = 1\n         let re = Regex::new(r"\\n;; ANSWER SECTION:\\n127.0.0.1.xip.io.\\s+\\d+\\s+IN\\s+A\\s+127.0.0.1")\n             .unwrap();\n         for port in &server.udp_ports {\n-            assert!(re.is_match(&dig("127.0.0.1.xip.io", Qprotocol::UDP, "127.0.0.1", *port).stdout));\n+            let output = dig("127.0.0.1.xip.io", Qprotocol::UDP, "127.0.0.1", *port).stdout;\n+            assert!(re.is_match(&output));\n+        }\n+        for port in &server.tcp_ports {\n+            let output = dig("127.0.0.1.xip.io", Qprotocol::TCP, "127.0.0.1", *port).stdout;\n+            assert!(re.is_match(&output));\n         }\n-        server.done();\n     }\n }\n', 'commit_subject': 'Add a function that uses coredns as a test backend', 'repo_name': 'jedisct1/edgedns', 'committer': {'name': 'Frederik Deweerdt', 'email': '16302676d30d62ef79ae78dacc43d439d75c284f@fastly.com', 'time_sec': '1498510719', 'tz_offset': '-420', 'date': {'seconds': '1498510719'}}, 'commit_tree': 'c187d8dcbf41803f6d01c2cd10a2f180c1b0b0f2', 'commit_message': 'Add a function that uses coredns as a test backend\n', 'language_name': 'Rust', 'license': 'isc', 'hunks': '@@ -1,45 +1,77 @@\n extern crate libedgedns;\n extern crate nix;\n extern crate regex;\n-\n+extern crate tempfile;\n \n #[cfg(test)]\n mod test {\n     extern crate env_logger;\n     use libedgedns::{Config, EdgeDNS};\n-    use std::env;\n-    use std::os::unix::io::RawFd;\n+\n     use nix::sys::signal::{kill, SIGKILL};\n     use nix::sys::ioctl::libc::pid_t;\n-    use nix::unistd::*;\n+    use nix::unistd::{fork, read, ForkResult, dup2};\n+    use nix::sys::ioctl::libc::alarm;\n     use nix::sys::socket::{socketpair, AddressFamily, SockType, SockFlag};\n+\n     use regex::Regex;\n-    use std::process::{Output, Command, ExitStatus};\n+\n+    use std::env;\n+    use std::io::Write;\n+    use std::process::{exit, Output, Command, ExitStatus};\n+    use std::os::unix::io::RawFd;\n+    use std::os::unix::process::CommandExt;\n     use std::string::String;\n+    use std::time::Duration;\n+\n+    use tempfile::NamedTempFile;\n \n     struct EdgeDNSInstance {\n-        stdout: RawFd,\n-        pid: pid_t,\n+        server: Server,\n         udp_ports: Vec<u16>,\n+        tcp_ports: Vec<u16>,\n+    }\n+\n+    struct Server {\n+        pid: pid_t,\n+        startup_text: String,\n+        output: RawFd,\n+    }\n+    impl Server {\n+            pub fn new() -> Server {\n+                Server{pid: 0, startup_text: String::from(""), output: 0}\n+            }\n     }\n-    impl EdgeDNSInstance {\n-        pub fn done(&self) {\n-            kill(self.pid, SIGKILL).expect("kill failed")\n+\n+    impl Drop for Server {\n+        fn drop(&mut self) {\n+            if self.pid != 0 {\n+                kill(self.pid, SIGKILL).expect("kill failed")\n+            }\n         }\n     }\n-    fn spawn_edgedns(cfg_str: &str) -> EdgeDNSInstance {\n-        let mut ret = EdgeDNSInstance {\n+\n+    fn spawn_server<F1, F2>(child_fn: F1, mut is_ready: F2, timeout: Duration) -> Server\n+        where F1: Fn() -> (),\n+              F2: FnMut(&str, pid_t) -> bool\n+    {\n+\n+        let mut server = Server {\n             pid: 0,\n-            stdout: 0,\n-            udp_ports: Vec::new(),\n+            output: 0,\n+            startup_text: String::new(),\n         };\n         let (fdc, fdp) = socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty())\n             .expect("socketpair");\n         match fork().expect("fork failed") {\n             ForkResult::Parent { child } => {\n-                ret.pid = child;\n-                ret.stdout = fdp;\n-                let mut s = String::new();\n+                server.pid = child;\n+                server.output = fdp;\n+\n+                /* FIXME: a non-unsafe way to do this */\n+                unsafe {\n+                    alarm(timeout.as_secs() as u32);\n+                }\n                 loop {\n                     let mut buf = [0; 1];\n                     let res = read(fdp, &mut buf);\n@@ -48,29 +80,111 @@ mod test {\n                     }\n                     let res = res.unwrap();\n                     if res > 0 {\n-                        s.push(buf[0] as char);\n-\n+                        server.startup_text.push(buf[0] as char);\n                     }\n-                    if s.contains("UDP listener is ready") && s.contains("TCP listener is ready") {\n+                    if is_ready(&server.startup_text, child) {\n                         break;\n                     }\n                 }\n-                let re = Regex::new(r"Created a UDP socket: (\\d+)").unwrap();\n-                for cap in re.captures_iter(&s) {\n-                    ret.udp_ports.push(cap[1].parse::<u16>().unwrap());\n+                unsafe {\n+                    alarm(0);\n                 }\n             }\n             ForkResult::Child => {\n-                env::set_var("RUST_LOG", "info");\n-                dup2(fdc, 2).expect("dup2 failed"); /* have env_logger log to the socketpair */\n-                env_logger::init().expect("Failed to init logger");\n-                let config = Config::from_string(cfg_str);\n-                assert!(config.is_ok());\n-                EdgeDNS::new(config.unwrap());\n+                dup2(fdc, 1).expect("dup2 failed");\n+                dup2(fdc, 2).expect("dup2 failed");\n+                child_fn();\n+                println!("The child exited");\n+                exit(0);\n+            }\n+        }\n+        server\n+    }\n+\n+    fn spawn_edgedns(cfg_str: &str) -> EdgeDNSInstance {\n+        let mut ret = EdgeDNSInstance {\n+            udp_ports: Vec::new(),\n+            tcp_ports: Vec::new(),\n+            server: spawn_server(|| {\n+                                     env::set_var("RUST_LOG", "info");\n+                                     env_logger::init().expect("Failed to init logger");\n+                                     let config = Config::from_string(cfg_str);\n+                                     assert!(config.is_ok());\n+                                     EdgeDNS::new(config.unwrap());\n+                                 },\n+                                 |out, _| {\n+                                     out.contains("UDP listener is ready") &&\n+                                     out.contains("TCP listener is ready")\n+                                 },\n+                                 Duration::new(5, 0)),\n+        };\n+\n+        for proto in ["TCP", "UDP"].iter() {\n+            let re = Regex::new(&format!(r"Created a {} socket: (\\d+)", proto)).unwrap();\n+            for cap in re.captures_iter(&ret.server.startup_text) {\n+                let port = cap[1].parse::<u16>().unwrap();\n+                if *proto == "UDP" {\n+                    ret.udp_ports.push(port);\n+                } else {\n+                    ret.tcp_ports.push(port);\n+                }\n             }\n         }\n+\n+        ret\n+    }\n+\n+    struct CoreDNS {\n+        server: Server,\n+        udp_port: u16,\n+    }\n+\n+    fn spawn_coredns(domain: &str, zone_str: &str) -> CoreDNS {\n+        let mut conf_file = NamedTempFile::new().unwrap();\n+        let mut zone_file = NamedTempFile::new().unwrap();\n+        zone_file.write_all(zone_str.as_bytes());\n+        let zfile_path = zone_file.path().to_str().unwrap();\n+        let conf_str = format!(r#"\n+{}:0 {{\n+    file {}\n+    errors stdout\n+    log stdout\n+}}\n+"#, domain, zfile_path);\n+        conf_file.write_all(conf_str.as_bytes());\n+        let cfile_path = conf_file.path().to_str().unwrap();\n+        let mut ret = CoreDNS {\n+            udp_port : 0,\n+            server: Server::new(),\n+        };\n+        ret.server = spawn_server(|| {\n+                let cmd = Command::new("coredns")\n+                    .args(&["-log", "-dns.port", "0", "-conf", cfile_path])\n+                    .exec();\n+                ::std::process::exit(1);\n+            },\n+            |out, pid| {\n+                if !out.contains("CoreDNS-008") {\n+                    return false;\n+                }\n+                let lsof = Command::new("lsof")\n+                    .arg("-p")\n+                    .arg(&format!("{}", pid))\n+                    .output();\n+                let lsof = String::from_utf8(lsof.unwrap().stdout).unwrap();\n+\n+                let re = Regex::new(r"0t0\\s+UDP \\*:(\\d+)").unwrap();\n+                let mut udp_port: u16 = 0;\n+                for cap in re.captures_iter(&lsof) {\n+                    udp_port = cap[1].parse::<u16>().unwrap();\n+                }\n+                ret.udp_port = udp_port;\n+                true\n+            },\n+            Duration::new(5, 0));\n         ret\n     }\n+\n     enum Qprotocol {\n         UDP,\n         TCP,\n@@ -81,13 +195,14 @@ mod test {\n         status: ExitStatus,\n     }\n     fn dig(query: &str, proto: Qprotocol, server: &str, port: u16) -> CmdOutput {\n-        let output = Command::new("dig")\n-            .arg(query)\n-            .arg(format!("@{}", server))\n-            .arg("-p")\n-            .arg(format!("{}", port))\n-            .output()\n-            .unwrap();\n+        let server = format!("@{}", server);\n+        let port = format!("{}", port);\n+        let mut args = vec![query, &server, "-p", &port];\n+        match proto {\n+            Qprotocol::TCP => args.push("+tcp"),\n+            _ => (),\n+        }\n+        let output = Command::new("dig").args(args).output().unwrap();\n         let mut stdout = String::new();\n         let mut stderr = String::new();\n         match String::from_utf8(output.stdout) {\n@@ -106,6 +221,49 @@ mod test {\n     }\n \n     /* tests */\n+    #[test]\n+    fn coredns_test() {\n+        let coredns = spawn_coredns("example.com", r#"\n+$ORIGIN example.com.     ; designates the start of this zone file in the namespace\n+$TTL 1h                  ; default expiration time of all resource records without their own TTL value\n+example.com.  IN  SOA   ns.example.com. username.example.com. ( 2007120710 1d 2h 4w 1h )\n+example.com.  IN  NS    ns                    ; ns.example.com is a nameserver for example.com\n+example.com.  IN  NS    ns.somewhere.example. ; ns.somewhere.example is a backup nameserver for example.com\n+example.com.  IN  MX    10 mail.example.com.  ; mail.example.com is the mailserver for example.com\n+@             IN  MX    20 mail2.example.com. ; equivalent to above line, "@" represents zone origin\n+@             IN  MX    50 mail3              ; equivalent to above line, but using a relative host name\n+example.com.  IN  A     192.0.2.1             ; IPv4 address for example.com\n+IN  AAAA  2001:db8:10::1        ; IPv6 address for example.com\n+ns            IN  A     192.0.2.2             ; IPv4 address for ns.example.com\n+IN  AAAA  2001:db8:10::2        ; IPv6 address for ns.example.com\n+www           IN  CNAME example.com.          ; www.example.com is an alias for example.com\n+wwwtest       IN  CNAME www                   ; wwwtest.example.com is another alias for www.example.com\n+mail          IN  A     192.0.2.3             ; IPv4 address for mail.example.com\n+mail2         IN  A     192.0.2.4             ; IPv4 address for mail2.example.com\n+mail3         IN  A     192.0.2.5             ; IPv4 address for mail3.example.com\n+"#);\n+        let cfg = format!(r#"\n+[upstream]\n+servers = ["127.0.0.1:{}"]\n+[network]\n+listen = "127.0.0.1:0"\n+udp_ports = 1\n+[global]\n+threads_udp = 1\n+threads_tcp = 1\n+"#, coredns.udp_port);\n+        let server = spawn_edgedns(&cfg);\n+        let re = Regex::new(r"\\n;; ANSWER SECTION:\\nmail.example.com.\\s+\\d+\\s+IN\\s+A\\s+192.0.2.3").unwrap();\n+        for port in &server.udp_ports {\n+            let output = dig("mail.example.com", Qprotocol::UDP, "127.0.0.1", *port).stdout;\n+            assert!(re.is_match(&output));\n+        }\n+        for port in &server.tcp_ports {\n+            let output = dig("127.0.0.1.xip.io", Qprotocol::TCP, "127.0.0.1", *port).stdout;\n+            println!("{}", output);\n+            assert!(re.is_match(&output));\n+        }\n+    }\n \n     #[test]\n     fn empty_config() {\n@@ -120,7 +278,6 @@ threads_udp = 1\n threads_tcp = 1\n "#;\n         let server = spawn_edgedns(&cfg);\n-        server.done();\n     }\n \n     #[test]\n@@ -139,8 +296,12 @@ threads_tcp = 1\n         let re = Regex::new(r"\\n;; ANSWER SECTION:\\n127.0.0.1.xip.io.\\s+\\d+\\s+IN\\s+A\\s+127.0.0.1")\n             .unwrap();\n         for port in &server.udp_ports {\n-            assert!(re.is_match(&dig("127.0.0.1.xip.io", Qprotocol::UDP, "127.0.0.1", *port).stdout));\n+            let output = dig("127.0.0.1.xip.io", Qprotocol::UDP, "127.0.0.1", *port).stdout;\n+            assert!(re.is_match(&output));\n+        }\n+        for port in &server.tcp_ports {\n+            let output = dig("127.0.0.1.xip.io", Qprotocol::TCP, "127.0.0.1", *port).stdout;\n+            assert!(re.is_match(&output));\n         }\n-        server.done();\n     }\n }\n', 'addition_count': 200, 'deletion_count': 39, 'src_file': 'a/tests/tests.rs', 'tgt_file': 'b/tests/tests.rs', 'file_extension': '.rs', 'before_file': 'extern crate libedgedns;\nextern crate nix;\nextern crate regex;\n\n\n#[cfg(test)]\nmod test {\n    extern crate env_logger;\n    use libedgedns::{Config, EdgeDNS};\n    use std::env;\n    use std::os::unix::io::RawFd;\n    use nix::sys::signal::{kill, SIGKILL};\n    use nix::sys::ioctl::libc::pid_t;\n    use nix::unistd::*;\n    use nix::sys::socket::{socketpair, AddressFamily, SockType, SockFlag};\n    use regex::Regex;\n    use std::process::{Output, Command, ExitStatus};\n    use std::string::String;\n\n    struct EdgeDNSInstance {\n        stdout: RawFd,\n        pid: pid_t,\n        udp_ports: Vec<u16>,\n    }\n    impl EdgeDNSInstance {\n        pub fn done(&self) {\n            kill(self.pid, SIGKILL).expect("kill failed")\n        }\n    }\n    fn spawn_edgedns(cfg_str: &str) -> EdgeDNSInstance {\n        let mut ret = EdgeDNSInstance {\n            pid: 0,\n            stdout: 0,\n            udp_ports: Vec::new(),\n        };\n        let (fdc, fdp) = socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty())\n            .expect("socketpair");\n        match fork().expect("fork failed") {\n            ForkResult::Parent { child } => {\n                ret.pid = child;\n                ret.stdout = fdp;\n                let mut s = String::new();\n                loop {\n                    let mut buf = [0; 1];\n                    let res = read(fdp, &mut buf);\n                    alarm(timeout.as_secs() as u32);\n                }\n                    }\n                    let res = res.unwrap();\n                    if res > 0 {\n                        s.push(buf[0] as char);\n\n                    }\n                    if s.contains("UDP listener is ready") && s.contains("TCP listener is ready") {\n                        break;\n                    }\n                }\n                let re = Regex::new(r"Created a UDP socket: (\\d+)").unwrap();\n                for cap in re.captures_iter(&s) {\n                    ret.udp_ports.push(cap[1].parse::<u16>().unwrap());\n                }\n            }\n            ForkResult::Child => {\n                env::set_var("RUST_LOG", "info");\n                dup2(fdc, 2).expect("dup2 failed"); /* have env_logger log to the socketpair */\n                env_logger::init().expect("Failed to init logger");\n                let config = Config::from_string(cfg_str);\n                assert!(config.is_ok());\n                EdgeDNS::new(config.unwrap());\n            }\n        }\n        ret\n    }\n    enum Qprotocol {\n        UDP,\n        TCP,\n                let re = Regex::new(r"0t0\\s+UDP \\*:(\\d+)").unwrap();\n                let mut udp_port: u16 = 0;\n                for cap in re.captures_iter(&lsof) {\n                    udp_port = cap[1].parse::<u16>().unwrap();\n        status: ExitStatus,\n    }\n    fn dig(query: &str, proto: Qprotocol, server: &str, port: u16) -> CmdOutput {\n        let output = Command::new("dig")\n            .arg(query)\n            .arg(format!("@{}", server))\n            .arg("-p")\n            .arg(format!("{}", port))\n            .output()\n            .unwrap();\n        let mut stdout = String::new();\n        let mut stderr = String::new();\n        match String::from_utf8(output.stdout) {\n        stdout: String,\n        #[allow(dead_code)]\n        stderr: String,\n        #[allow(dead_code)]\n        status: ExitStatus,\n    }\n    fn dig(query: &str, proto: Qprotocol, server: &str, port: u16) -> CmdOutput {\n        let server = format!("@{}", server);\n        let port = format!("{}", port);\n        let mut args = vec![query, &server, "-p", &port];\n        match proto {\n            Qprotocol::TCP => args.push("+tcp"),\n    }\n\n    /* tests */\n\n    #[test]\n    fn empty_config() {\nlisten = "127.0.0.1:0"\n"#,\n            coredns.udp_port\n        );\n        let server = spawn_edgedns(&cfg);\n        let re = Regex::new(\n            r"\\n;; ANSWER SECTION:\\nmail.example.com.\\s+\\d+\\s+IN\\s+A\\s+192.0.2.3",\n        ).unwrap();\nthreads_tcp = 1\n"#;\n        let server = spawn_edgedns(&cfg);\n        server.done();\n    }\n\n    #[test]\n            println!("{}", output);\n            assert!(re.is_match(&output));\n        }\n    }\n\n    #[test]\n    fn empty_config() {\n        let cfg = r#"\n[upstream]\nservers = ["127.0.0.1:53"]\n[network]\nlisten = "127.0.0.1:0"\n        let re = Regex::new(r"\\n;; ANSWER SECTION:\\n127.0.0.1.xip.io.\\s+\\d+\\s+IN\\s+A\\s+127.0.0.1")\n            .unwrap();\n        for port in &server.udp_ports {\n            assert!(re.is_match(&dig("127.0.0.1.xip.io", Qprotocol::UDP, "127.0.0.1", *port).stdout));\n        }\n        server.done();\n    }\n}\n', 'message': 'Add a function that uses coredns as a test backend\n', 'author_name': 'Frederik Deweerdt', 'lang': 'rs'}