Repository: toml-rs/toml
Pull request #474 (2023-01-20 02:51:43)

test: Re-evaluate where tests live
Depending on the case, a serde tests could live in either or both
crates, so let's simplify by just saying all serde tests live in `toml`.
This as the added benefit in that it makes sure the newtype wrappers
don't break things.

Similar for parser tests living in `toml_edit`


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index fe2abd29..faae06b9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -480,6 +480,12 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
+[[package]]
+name = "lexopt"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401"
+
 [[package]]
 name = "libc"
 version = "0.2.133"
@@ -946,6 +952,7 @@ name = "toml_benchmarks"
 version = "0.0.0"
 dependencies = [
  "criterion",
+ "lexopt",
  "serde",
  "serde_json",
  "toml 0.5.10",
diff --git a/crates/benchmarks/Cargo.toml b/crates/benchmarks/Cargo.toml
index d2b91637..4de123b7 100644
--- a/crates/benchmarks/Cargo.toml
+++ b/crates/benchmarks/Cargo.toml
@@ -12,6 +12,7 @@ toml_edit = { path = "../toml_edit" }
 toml_old = { version = "0.5.10", package = "toml" }
 serde = { version = "1.0.152", features = ["derive"] }
 serde_json = "1.0.91"
+lexopt = "0.3.0"
 
 [[bench]]
 name = "cargo"
diff --git a/crates/toml_edit/examples/bench_easy_de.rs b/crates/benchmarks/examples/bench.rs
similarity index 75%
rename from crates/toml_edit/examples/bench_easy_de.rs
rename to crates/benchmarks/examples/bench.rs
index 4f18265f..010c0924 100644
--- a/crates/toml_edit/examples/bench_easy_de.rs
+++ b/crates/benchmarks/examples/bench.rs
@@ -1,11 +1,167 @@
-#[cfg(not(feature = "easy"))]
-fn main() {
-    println!("Add `--features easy`");
+fn main() -> Result<(), lexopt::Error> {
+    let args = Args::parse()?;
+
+    match args.parser {
+        Parser::Document => {
+            let _doc = CARGO_MANIFEST.parse::<toml_edit::Document>().unwrap();
+            #[cfg(debug_assertions)] // Don't interefere with profiling
+            dbg!(_doc);
+        }
+        Parser::De => {
+            let _doc = toml::from_str::<manifest::Manifest>(CARGO_MANIFEST).unwrap();
+            #[cfg(debug_assertions)] // Don't interefere with profiling
+            dbg!(_doc);
+        }
+        Parser::Table => {
+            let _doc = CARGO_MANIFEST.parse::<toml::Table>().unwrap();
+            #[cfg(debug_assertions)] // Don't interefere with profiling
+            dbg!(_doc);
+        }
+    }
+    Ok(())
+}
+
+struct Args {
+    parser: Parser,
+}
+
+impl Args {
+    fn parse() -> Result<Self, lexopt::Error> {
+        use lexopt::prelude::*;
+
+        let mut parser = Parser::Document;
+
+        let mut args = lexopt::Parser::from_env();
+        while let Some(arg) = args.next()? {
+            match arg {
+                Long("parser") => {
+                    let value = args.value()?;
+                    parser = match &value.to_str() {
+                        Some("document") => Parser::Document,
+                        Some("de") => Parser::De,
+                        Some("table") => Parser::Table,
+                        _ => {
+                            return Err(lexopt::Error::UnexpectedValue {
+                                option: "parser".to_owned(),
+                                value: value.clone(),
+                            });
+                        }
+                    };
+                }
+                _ => return Err(arg.unexpected()),
+            }
+        }
+
+        Ok(Self { parser })
+    }
 }
 
-#[cfg(feature = "easy")]
-fn main() {
-    const CARGO_MANIFEST: &str = r#"
+enum Parser {
+    Document,
+    De,
+    Table,
+}
+
+mod manifest {
+    use std::collections::HashMap;
+
+    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+    #[serde(rename_all = "kebab-case")]
+    pub struct Manifest {
+        package: Package,
+        #[serde(default)]
+        lib: Option<Lib>,
+        #[serde(default)]
+        bin: Vec<Bin>,
+        #[serde(default)]
+        features: HashMap<String, Vec<String>>,
+        #[serde(default)]
+        dependencies: HashMap<String, Dependency>,
+        #[serde(default)]
+        build_dependencies: HashMap<String, Dependency>,
+        #[serde(default)]
+        dev_dependencies: HashMap<String, Dependency>,
+        #[serde(default)]
+        target: HashMap<String, Target>,
+    }
+
+    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+    #[serde(rename_all = "kebab-case")]
+    pub struct Package {
+        name: String,
+        version: String,
+        #[serde(default)]
+        edition: Option<String>,
+        #[serde(default)]
+        authors: Vec<String>,
+        #[serde(default)]
+        license: Option<String>,
+        #[serde(default)]
+        homepage: Option<String>,
+        #[serde(default)]
+        repository: Option<String>,
+        #[serde(default)]
+        documentation: Option<String>,
+        #[serde(default)]
+        readme: Option<String>,
+        #[serde(default)]
+        description: Option<String>,
+    }
+
+    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+    #[serde(rename_all = "kebab-case")]
+    pub struct Lib {
+        name: String,
+        #[serde(default)]
+        path: Option<String>,
+    }
+
+    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+    #[serde(rename_all = "kebab-case")]
+    pub struct Bin {
+        name: String,
+        #[serde(default)]
+        test: bool,
+        #[serde(default)]
+        doc: bool,
+    }
+
+    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+    #[serde(rename_all = "kebab-case")]
+    #[serde(untagged)]
+    pub enum Dependency {
+        Version(String),
+        Full(DependencyFull),
+    }
+
+    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+    #[serde(rename_all = "kebab-case")]
+    pub struct DependencyFull {
+        #[serde(default)]
+        version: Option<String>,
+        #[serde(default)]
+        path: Option<String>,
+        #[serde(default)]
+        default_features: bool,
+        #[serde(default)]
+        optional: bool,
+        #[serde(default)]
+        features: Vec<String>,
+    }
+
+    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+    #[serde(rename_all = "kebab-case")]
+    pub struct Target {
+        #[serde(default)]
+        dependencies: HashMap<String, Dependency>,
+        #[serde(default)]
+        build_dependencies: HashMap<String, Dependency>,
+        #[serde(default)]
+        dev_dependencies: HashMap<String, Dependency>,
+    }
+}
+
+const CARGO_MANIFEST: &str = r#"
 [package]
 name = "cargo"
 version = "0.57.0"
@@ -124,106 +280,3 @@ deny-warnings = []
 vendored-openssl = ["openssl/vendored"]
 pretty-env-logger = ["pretty_env_logger"]
 "#;
-    #[cfg(feature = "easy")]
-    let _ = toml_edit::easy::from_str::<manifest::Manifest>(CARGO_MANIFEST).unwrap();
-}
-
-#[cfg(feature = "easy")]
-mod manifest {
-    use std::collections::HashMap;
-
-    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
-    #[serde(rename_all = "kebab-case")]
-    pub struct Manifest {
-        package: Package,
-        #[serde(default)]
-        lib: Option<Lib>,
-        #[serde(default)]
-        bin: Vec<Bin>,
-        #[serde(default)]
-        features: HashMap<String, Vec<String>>,
-        #[serde(default)]
-        dependencies: HashMap<String, Dependency>,
-        #[serde(default)]
-        build_dependencies: HashMap<String, Dependency>,
-        #[serde(default)]
-        dev_dependencies: HashMap<String, Dependency>,
-        #[serde(default)]
-        target: HashMap<String, Target>,
-    }
-
-    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
-    #[serde(rename_all = "kebab-case")]
-    pub struct Package {
-        name: String,
-        version: String,
-        #[serde(default)]
-        edition: Option<String>,
-        #[serde(default)]
-        authors: Vec<String>,
-        #[serde(default)]
-        license: Option<String>,
-        #[serde(default)]
-        homepage: Option<String>,
-        #[serde(default)]
-        repository: Option<String>,
-        #[serde(default)]
-        documentation: Option<String>,
-        #[serde(default)]
-        readme: Option<String>,
-        #[serde(default)]
-        description: Option<String>,
-    }
-
-    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
-    #[serde(rename_all = "kebab-case")]
-    pub struct Lib {
-        name: String,
-        #[serde(default)]
-        path: Option<String>,
-    }
-
-    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
-    #[serde(rename_all = "kebab-case")]
-    pub struct Bin {
-        name: String,
-        #[serde(default)]
-        test: bool,
-        #[serde(default)]
-        doc: bool,
-    }
-
-    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
-    #[serde(rename_all = "kebab-case")]
-    #[serde(untagged)]
-    pub enum Dependency {
-        Version(String),
-        Full(DependencyFull),
-    }
-
-    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
-    #[serde(rename_all = "kebab-case")]
-    pub struct DependencyFull {
-        #[serde(default)]
-        version: Option<String>,
-        #[serde(default)]
-        path: Option<String>,
-        #[serde(default)]
-        default_features: bool,
-        #[serde(default)]
-        optional: bool,
-        #[serde(default)]
-        features: Vec<String>,
-    }
-
-    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
-    #[serde(rename_all = "kebab-case")]
-    pub struct Target {
-        #[serde(default)]
-        dependencies: HashMap<String, Dependency>,
-        #[serde(default)]
-        build_dependencies: HashMap<String, Dependency>,
-        #[serde(default)]
-        dev_dependencies: HashMap<String, Dependency>,
-    }
-}
diff --git a/crates/toml_edit/Cargo.toml b/crates/toml_edit/Cargo.toml
index 91dac946..0d907263 100644
--- a/crates/toml_edit/Cargo.toml
+++ b/crates/toml_edit/Cargo.toml
@@ -70,14 +70,6 @@ harness = false
 name = "encoder_compliance"
 harness = false
 
-[[test]]
-name = "easy_decoder_compliance"
-harness = false
-
-[[test]]
-name = "easy_encoder_compliance"
-harness = false
-
 [[test]]
 name = "invalid"
 harness = false
diff --git a/crates/toml_edit/examples/bench.rs b/crates/toml_edit/examples/bench.rs
deleted file mode 100644
index 16bd6814..00000000
--- a/crates/toml_edit/examples/bench.rs
+++ /dev/null
@@ -1,124 +0,0 @@
-fn main() {
-    const CARGO_MANIFEST: &str = r#"
-[package]
-name = "cargo"
-version = "0.57.0"
-edition = "2018"
-authors = ["Yehuda Katz <wycats@gmail.com>",
-           "Carl Lerche <me@carllerche.com>",
-           "Alex Crichton <alex@alexcrichton.com>"]
-license = "MIT OR Apache-2.0"
-homepage = "https://crates.io"
-repository = "https://github.com/rust-lang/cargo"
-documentation = "https://docs.rs/cargo"
-readme = "README.md"
-description = """
-Cargo, a package manager for Rust.
-"""
-
-[lib]
-name = "cargo"
-path = "src/cargo/lib.rs"
-
-[dependencies]
-atty = "0.2"
-bytesize = "1.0"
-cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" }
-cargo-util = { path = "crates/cargo-util", version = "0.1.1" }
-crates-io = { path = "crates/crates-io", version = "0.33.0" }
-crossbeam-utils = "0.8"
-curl = { version = "0.4.38", features = ["http2"] }
-curl-sys = "0.4.45"
-env_logger = "0.9.0"
-pretty_env_logger = { version = "0.4", optional = true }
-anyhow = "1.0"
-filetime = "0.2.9"
-flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
-git2 = "0.13.16"
-git2-curl = "0.14.1"
-glob = "0.3.0"
-hex = "0.4"
-home = "0.5"
-humantime = "2.0.0"
-ignore = "0.4.7"
-lazy_static = "1.2.0"
-jobserver = "0.1.24"
-lazycell = "1.2.0"
-libc = "0.2"
-log = "0.4.6"
-libgit2-sys = "0.12.18"
-memchr = "2.1.3"
-num_cpus = "1.0"
-opener = "0.5"
-percent-encoding = "2.0"
-rustfix = "0.6.0"
-semver = { version = "1.0.3", features = ["serde"] }
-serde = { version = "1.0.123", features = ["derive"] }
-serde_ignored = "0.1.0"
-serde_json = { version = "1.0.30", features = ["raw_value"] }
-shell-escape = "0.1.4"
-strip-ansi-escapes = "0.1.0"
-tar = { version = "0.4.35", default-features = false }
-tempfile = "3.0"
-termcolor = "1.1"
-toml = "0.5.7"
-unicode-xid = "0.2.0"
-url = "2.2.2"
-walkdir = "2.2"
-clap = "2.31.2"
-unicode-width = "0.1.5"
-openssl = { version = '0.10.11', optional = true }
-im-rc = "15.0.0"
-itertools = "0.10.0"
-
-# A noop dependency that changes in the Rust repository, it's a bit of a hack.
-# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
-# for more information.
-rustc-workspace-hack = "1.0.0"
-
-[target.'cfg(windows)'.dependencies]
-fwdansi = "1.1.0"
-
-[target.'cfg(windows)'.dependencies.winapi]
-version = "0.3"
-features = [
-  "basetsd",
-  "handleapi",
-  "jobapi",
-  "jobapi2",
-  "memoryapi",
-  "minwindef",
-  "ntdef",
-  "ntstatus",
-  "processenv",
-  "processthreadsapi",
-  "psapi",
-  "synchapi",
-  "winerror",
-  "winbase",
-  "wincon",
-  "winnt",
-]
-
-[dev-dependencies]
-cargo-test-macro = { path = "crates/cargo-test-macro" }
-cargo-test-support = { path = "crates/cargo-test-support" }
-
-[build-dependencies]
-flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
-tar = { version = "0.4.26", default-features = false }
-
-[[bin]]
-name = "cargo"
-test = false
-doc = false
-
-[features]
-deny-warnings = []
-vendored-openssl = ["openssl/vendored"]
-pretty-env-logger = ["pretty_env_logger"]
-"#;
-    let _doc = CARGO_MANIFEST.parse::<toml_edit::Document>().unwrap();
-    #[cfg(debug_assertions)] // Don't interefere with profiling
-    dbg!(_doc);
-}
diff --git a/crates/toml_edit/examples/bench_easy_value.rs b/crates/toml_edit/examples/bench_easy_value.rs
deleted file mode 100644
index 3921912e..00000000
--- a/crates/toml_edit/examples/bench_easy_value.rs
+++ /dev/null
@@ -1,129 +0,0 @@
-#[cfg(not(feature = "easy"))]
-fn main() {
-    println!("Add `--features easy`");
-}
-
-#[cfg(feature = "easy")]
-fn main() {
-    const CARGO_MANIFEST: &str = r#"
-[package]
-name = "cargo"
-version = "0.57.0"
-edition = "2018"
-authors = ["Yehuda Katz <wycats@gmail.com>",
-           "Carl Lerche <me@carllerche.com>",
-           "Alex Crichton <alex@alexcrichton.com>"]
-license = "MIT OR Apache-2.0"
-homepage = "https://crates.io"
-repository = "https://github.com/rust-lang/cargo"
-documentation = "https://docs.rs/cargo"
-readme = "README.md"
-description = """
-Cargo, a package manager for Rust.
-"""
-
-[lib]
-name = "cargo"
-path = "src/cargo/lib.rs"
-
-[dependencies]
-atty = "0.2"
-bytesize = "1.0"
-cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" }
-cargo-util = { path = "crates/cargo-util", version = "0.1.1" }
-crates-io = { path = "crates/crates-io", version = "0.33.0" }
-crossbeam-utils = "0.8"
-curl = { version = "0.4.38", features = ["http2"] }
-curl-sys = "0.4.45"
-env_logger = "0.9.0"
-pretty_env_logger = { version = "0.4", optional = true }
-anyhow = "1.0"
-filetime = "0.2.9"
-flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
-git2 = "0.13.16"
-git2-curl = "0.14.1"
-glob = "0.3.0"
-hex = "0.4"
-home = "0.5"
-humantime = "2.0.0"
-ignore = "0.4.7"
-lazy_static = "1.2.0"
-jobserver = "0.1.24"
-lazycell = "1.2.0"
-libc = "0.2"
-log = "0.4.6"
-libgit2-sys = "0.12.18"
-memchr = "2.1.3"
-num_cpus = "1.0"
-opener = "0.5"
-percent-encoding = "2.0"
-rustfix = "0.6.0"
-semver = { version = "1.0.3", features = ["serde"] }
-serde = { version = "1.0.123", features = ["derive"] }
-serde_ignored = "0.1.0"
-serde_json = { version = "1.0.30", features = ["raw_value"] }
-shell-escape = "0.1.4"
-strip-ansi-escapes = "0.1.0"
-tar = { version = "0.4.35", default-features = false }
-tempfile = "3.0"
-termcolor = "1.1"
-toml = "0.5.7"
-unicode-xid = "0.2.0"
-url = "2.2.2"
-walkdir = "2.2"
-clap = "2.31.2"
-unicode-width = "0.1.5"
-openssl = { version = '0.10.11', optional = true }
-im-rc = "15.0.0"
-itertools = "0.10.0"
-
-# A noop dependency that changes in the Rust repository, it's a bit of a hack.
-# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
-# for more information.
-rustc-workspace-hack = "1.0.0"
-
-[target.'cfg(windows)'.dependencies]
-fwdansi = "1.1.0"
-
-[target.'cfg(windows)'.dependencies.winapi]
-version = "0.3"
-features = [
-  "basetsd",
-  "handleapi",
-  "jobapi",
-  "jobapi2",
-  "memoryapi",
-  "minwindef",
-  "ntdef",
-  "ntstatus",
-  "processenv",
-  "processthreadsapi",
-  "psapi",
-  "synchapi",
-  "winerror",
-  "winbase",
-  "wincon",
-  "winnt",
-]
-
-[dev-dependencies]
-cargo-test-macro = { path = "crates/cargo-test-macro" }
-cargo-test-support = { path = "crates/cargo-test-support" }
-
-[build-dependencies]
-flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
-tar = { version = "0.4.26", default-features = false }
-
-[[bin]]
-name = "cargo"
-test = false
-doc = false
-
-[features]
-deny-warnings = []
-vendored-openssl = ["openssl/vendored"]
-pretty-env-logger = ["pretty_env_logger"]
-"#;
-    #[cfg(feature = "easy")]
-    let _ = CARGO_MANIFEST.parse::<toml_edit::easy::Value>().unwrap();
-}


Test patch:
diff --git a/crates/toml/tests/decoder_compliance.rs b/crates/toml/tests/decoder_compliance.rs
index fb1b2e8b..e6881dad 100644
--- a/crates/toml/tests/decoder_compliance.rs
+++ b/crates/toml/tests/decoder_compliance.rs
@@ -5,18 +5,12 @@ fn main() {
     let mut harness = toml_test_harness::DecoderHarness::new(decoder);
     harness
         .ignore([
-            "invalid/control/comment-cr.toml",
-            "invalid/control/comment-del.toml",
-            "invalid/datetime/hour-over.toml",
-            "invalid/integer/positive-bin.toml",
-            "invalid/integer/positive-hex.toml",
-            "valid/inline-table/newline.toml",
             "valid/spec/float-0.toml",
-            "valid/spec/table-9.toml",
             // Unreleased
             "valid/string/escape-esc.toml",
             "valid/string/hex-escape.toml",
             "valid/datetime/no-seconds.toml",
+            "valid/inline-table/newline.toml",
         ])
         .unwrap();
     harness.test();
diff --git a/crates/toml/tests/encoder_compliance.rs b/crates/toml/tests/encoder_compliance.rs
index 5621397a..9d77448a 100644
--- a/crates/toml/tests/encoder_compliance.rs
+++ b/crates/toml/tests/encoder_compliance.rs
@@ -5,13 +5,6 @@ fn main() {
     let encoder = encoder::Encoder;
     let decoder = decoder::Decoder;
     let mut harness = toml_test_harness::EncoderHarness::new(encoder, decoder);
-    harness
-        .ignore([
-            "valid/array/mixed-string-table.toml",
-            "valid/inline-table/nest.toml",
-            "valid/spec/float-0.toml",
-            "valid/spec/array-0.toml",
-        ])
-        .unwrap();
+    harness.ignore(["valid/spec/float-0.toml"]).unwrap();
     harness.test();
 }
diff --git a/crates/toml/tests/testsuite/invalid_misc.rs b/crates/toml/tests/testsuite/invalid_misc.rs
deleted file mode 100644
index c7497343..00000000
--- a/crates/toml/tests/testsuite/invalid_misc.rs
+++ /dev/null
@@ -1,141 +0,0 @@
-macro_rules! bad {
-    ($toml:expr, $msg:expr) => {
-        match $toml.parse::<toml::Value>() {
-            Ok(s) => panic!("parsed to: {:#?}", s),
-            Err(e) => snapbox::assert_eq($msg, e.to_string()),
-        }
-    };
-}
-
-#[test]
-fn bad() {
-    bad!(
-        "a = 01",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = 01
-  |      ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "a = 1__1",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = 1__1
-  |       ^
-invalid integer
-expected digit
-"
-    );
-    bad!(
-        "a = 1_",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = 1_
-  |       ^
-invalid integer
-expected digit
-"
-    );
-    bad!(
-        "''",
-        "\
-TOML parse error at line 1, column 3
-  |
-1 | ''
-  |   ^
-expected `.`, `=`
-"
-    );
-    bad!(
-        "a = 9e99999",
-        "\
-TOML parse error at line 1, column 5
-  |
-1 | a = 9e99999
-  |     ^
-invalid floating-point number
-"
-    );
-
-    bad!(
-        "a = \"\u{7f}\"",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = \"\u{7f}\"
-  |      ^
-invalid basic string
-"
-    );
-    bad!(
-        "a = '\u{7f}'",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = '\u{7f}'
-  |      ^
-invalid literal string
-"
-    );
-
-    bad!(
-        "a = -0x1",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = -0x1
-  |       ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "a = 0x-1",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = 0x-1
-  |       ^
-invalid hexadecimal integer
-"
-    );
-
-    // Dotted keys.
-    bad!(
-        "a.b.c = 1
-         a.b = 2
-        ",
-        "\
-TOML parse error at line 2, column 10
-  |
-2 |          a.b = 2
-  |          ^
-duplicate key `b` in document root
-"
-    );
-    bad!(
-        "a = 1
-         a.b = 2",
-        "\
-TOML parse error at line 2, column 10
-  |
-2 |          a.b = 2
-  |          ^
-dotted key `a` attempted to extend non-table type (integer)
-"
-    );
-    bad!(
-        "a = {k1 = 1, k1.name = \"joe\"}",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = {k1 = 1, k1.name = \"joe\"}
-  |      ^
-dotted key `k1` attempted to extend non-table type (integer)
-"
-    );
-}
diff --git a/crates/toml/tests/testsuite/main.rs b/crates/toml/tests/testsuite/main.rs
index 649c2c95..b7a99fb7 100644
--- a/crates/toml/tests/testsuite/main.rs
+++ b/crates/toml/tests/testsuite/main.rs
@@ -1,15 +1,12 @@
 #![recursion_limit = "256"]
 
-mod datetime;
 mod de_errors;
 mod display;
 mod display_tricky;
 mod enum_external_deserialize;
 mod float;
 mod formatting;
-mod invalid_misc;
 mod macros;
-mod parser;
 mod pretty;
 mod serde;
 mod spanned;
diff --git a/crates/toml/tests/testsuite/parser.rs b/crates/toml/tests/testsuite/parser.rs
deleted file mode 100644
index 0d2324b8..00000000
--- a/crates/toml/tests/testsuite/parser.rs
+++ /dev/null
@@ -1,1354 +0,0 @@
-use toml::Value;
-
-macro_rules! bad {
-    ($toml:expr, $msg:expr) => {
-        match $toml.parse::<toml::Value>() {
-            Ok(s) => panic!("parsed to: {:#?}", s),
-            Err(e) => snapbox::assert_eq($msg, e.to_string()),
-        }
-    };
-}
-
-#[test]
-fn crlf() {
-    "\
-     [project]\r\n\
-     \r\n\
-     name = \"splay\"\r\n\
-     version = \"0.1.0\"\r\n\
-     authors = [\"alex@crichton.co\"]\r\n\
-     \r\n\
-     [[lib]]\r\n\
-     \r\n\
-     path = \"lib.rs\"\r\n\
-     name = \"splay\"\r\n\
-     description = \"\"\"\
-     A Rust implementation of a TAR file reader and writer. This library does not\r\n\
-     currently handle compression, but it is abstract over all I/O readers and\r\n\
-     writers. Additionally, great lengths are taken to ensure that the entire\r\n\
-     contents are never required to be entirely resident in memory all at once.\r\n\
-     \"\"\"\
-     "
-    .parse::<Value>()
-    .unwrap();
-}
-
-#[test]
-fn fun_with_strings() {
-    let table = r#"
-bar = "\U00000000"
-key1 = "One\nTwo"
-key2 = """One\nTwo"""
-key3 = """
-One
-Two"""
-
-key4 = "The quick brown fox jumps over the lazy dog."
-key5 = """
-The quick brown \
-
-
-fox jumps over \
-the lazy dog."""
-key6 = """\
-   The quick brown \
-   fox jumps over \
-   the lazy dog.\
-   """
-# What you see is what you get.
-winpath  = 'C:\Users\nodejs\templates'
-winpath2 = '\\ServerX\admin$\system32\'
-quoted   = 'Tom "Dubs" Preston-Werner'
-regex    = '<\i\c*\s*>'
-
-regex2 = '''I [dw]on't need \d{2} apples'''
-lines  = '''
-The first newline is
-trimmed in raw strings.
-All other whitespace
-is preserved.
-'''
-"#
-    .parse::<Value>()
-    .unwrap();
-    assert_eq!(table["bar"].as_str(), Some("\0"));
-    assert_eq!(table["key1"].as_str(), Some("One\nTwo"));
-    assert_eq!(table["key2"].as_str(), Some("One\nTwo"));
-    assert_eq!(table["key3"].as_str(), Some("One\nTwo"));
-
-    let msg = "The quick brown fox jumps over the lazy dog.";
-    assert_eq!(table["key4"].as_str(), Some(msg));
-    assert_eq!(table["key5"].as_str(), Some(msg));
-    assert_eq!(table["key6"].as_str(), Some(msg));
-
-    assert_eq!(
-        table["winpath"].as_str(),
-        Some(r"C:\Users\nodejs\templates")
-    );
-    assert_eq!(
-        table["winpath2"].as_str(),
-        Some(r"\\ServerX\admin$\system32\")
-    );
-    assert_eq!(
-        table["quoted"].as_str(),
-        Some(r#"Tom "Dubs" Preston-Werner"#)
-    );
-    assert_eq!(table["regex"].as_str(), Some(r"<\i\c*\s*>"));
-    assert_eq!(
-        table["regex2"].as_str(),
-        Some(r"I [dw]on't need \d{2} apples")
-    );
-    assert_eq!(
-        table["lines"].as_str(),
-        Some(
-            "The first newline is\n\
-             trimmed in raw strings.\n\
-             All other whitespace\n\
-             is preserved.\n"
-        )
-    );
-}
-
-#[test]
-fn tables_in_arrays() {
-    let table = r#"
-[[foo]]
-#…
-[foo.bar]
-#…
-
-[[foo]] # ...
-#…
-[foo.bar]
-#...
-"#
-    .parse::<Value>()
-    .unwrap();
-    table["foo"][0]["bar"].as_table().unwrap();
-    table["foo"][1]["bar"].as_table().unwrap();
-}
-
-#[test]
-fn empty_table() {
-    let table = r#"
-[foo]"#
-        .parse::<Value>()
-        .unwrap();
-    table["foo"].as_table().unwrap();
-}
-
-#[test]
-fn fruit() {
-    let table = r#"
-[[fruit]]
-name = "apple"
-
-[fruit.physical]
-color = "red"
-shape = "round"
-
-[[fruit.variety]]
-name = "red delicious"
-
-[[fruit.variety]]
-name = "granny smith"
-
-[[fruit]]
-name = "banana"
-
-[[fruit.variety]]
-name = "plantain"
-"#
-    .parse::<Value>()
-    .unwrap();
-    assert_eq!(table["fruit"][0]["name"].as_str(), Some("apple"));
-    assert_eq!(table["fruit"][0]["physical"]["color"].as_str(), Some("red"));
-    assert_eq!(
-        table["fruit"][0]["physical"]["shape"].as_str(),
-        Some("round")
-    );
-    assert_eq!(
-        table["fruit"][0]["variety"][0]["name"].as_str(),
-        Some("red delicious")
-    );
-    assert_eq!(
-        table["fruit"][0]["variety"][1]["name"].as_str(),
-        Some("granny smith")
-    );
-    assert_eq!(table["fruit"][1]["name"].as_str(), Some("banana"));
-    assert_eq!(
-        table["fruit"][1]["variety"][0]["name"].as_str(),
-        Some("plantain")
-    );
-}
-
-#[test]
-fn stray_cr() {
-    bad!(
-        "\r",
-        "\
-TOML parse error at line 1, column 1
-  |
-1 | \r
-  | ^
-
-"
-    );
-    bad!(
-        "a = [ \r ]",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = [ \r
- ]
-  |       ^
-invalid array
-expected `]`
-"
-    );
-    bad!(
-        "a = \"\"\"\r\"\"\"",
-        "\
-TOML parse error at line 1, column 8
-  |
-1 | a = \"\"\"\r
-\"\"\"
-  |        ^
-invalid multiline basic string
-"
-    );
-    bad!(
-        "a = \"\"\"\\  \r  \"\"\"",
-        "\
-TOML parse error at line 1, column 10
-  |
-1 | a = \"\"\"\\  \r
-  \"\"\"
-  |          ^
-invalid escape sequence
-expected `b`, `f`, `n`, `r`, `t`, `u`, `U`, `\\`, `\"`
-"
-    );
-    bad!(
-        "a = '''\r'''",
-        "\
-TOML parse error at line 1, column 8
-  |
-1 | a = '''\r
-'''
-  |        ^
-invalid multiline literal string
-"
-    );
-    bad!(
-        "a = '\r'",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = '\r
-'
-  |      ^
-invalid literal string
-"
-    );
-    bad!(
-        "a = \"\r\"",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = \"\r
-\"
-  |      ^
-invalid basic string
-"
-    );
-}
-
-#[test]
-fn blank_literal_string() {
-    let table = "foo = ''".parse::<Value>().unwrap();
-    assert_eq!(table["foo"].as_str(), Some(""));
-}
-
-#[test]
-fn many_blank() {
-    let table = "foo = \"\"\"\n\n\n\"\"\"".parse::<Value>().unwrap();
-    assert_eq!(table["foo"].as_str(), Some("\n\n"));
-}
-
-#[test]
-fn literal_eats_crlf() {
-    let table = "
-        foo = \"\"\"\\\r\n\"\"\"
-        bar = \"\"\"\\\r\n   \r\n   \r\n   a\"\"\"
-    "
-    .parse::<Value>()
-    .unwrap();
-    assert_eq!(table["foo"].as_str(), Some(""));
-    assert_eq!(table["bar"].as_str(), Some("a"));
-}
-
-#[test]
-fn string_no_newline() {
-    bad!(
-        "a = \"\n\"",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = \"
-  |      ^
-invalid basic string
-"
-    );
-    bad!(
-        "a = '\n'",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = '
-  |      ^
-invalid literal string
-"
-    );
-}
-
-#[test]
-fn bad_leading_zeros() {
-    bad!(
-        "a = 00",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = 00
-  |      ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "a = -00",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = -00
-  |       ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "a = +00",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = +00
-  |       ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "a = 00.0",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = 00.0
-  |      ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "a = -00.0",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = -00.0
-  |       ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "a = +00.0",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = +00.0
-  |       ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "a = 9223372036854775808",
-        "\
-TOML parse error at line 1, column 5
-  |
-1 | a = 9223372036854775808
-  |     ^
-number too large to fit in target type
-"
-    );
-    bad!(
-        "a = -9223372036854775809",
-        "\
-TOML parse error at line 1, column 5
-  |
-1 | a = -9223372036854775809
-  |     ^
-number too small to fit in target type
-"
-    );
-}
-
-#[test]
-fn bad_floats() {
-    bad!(
-        "a = 0.",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = 0.
-  |       ^
-invalid floating-point number
-expected digit
-"
-    );
-    bad!(
-        "a = 0.e",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = 0.e
-  |       ^
-invalid floating-point number
-expected digit
-"
-    );
-    bad!(
-        "a = 0.E",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | a = 0.E
-  |       ^
-invalid floating-point number
-expected digit
-"
-    );
-    bad!(
-        "a = 0.0E",
-        "\
-TOML parse error at line 1, column 9
-  |
-1 | a = 0.0E
-  |         ^
-invalid floating-point number
-"
-    );
-    bad!(
-        "a = 0.0e",
-        "\
-TOML parse error at line 1, column 9
-  |
-1 | a = 0.0e
-  |         ^
-invalid floating-point number
-"
-    );
-    bad!(
-        "a = 0.0e-",
-        "\
-TOML parse error at line 1, column 10
-  |
-1 | a = 0.0e-
-  |          ^
-invalid floating-point number
-"
-    );
-    bad!(
-        "a = 0.0e+",
-        "\
-TOML parse error at line 1, column 10
-  |
-1 | a = 0.0e+
-  |          ^
-invalid floating-point number
-"
-    );
-}
-
-#[test]
-fn floats() {
-    macro_rules! t {
-        ($actual:expr, $expected:expr) => {{
-            let f = format!("foo = {}", $actual);
-            println!("{}", f);
-            let a = f.parse::<Value>().unwrap();
-            assert_eq!(a["foo"].as_float().unwrap(), $expected);
-        }};
-    }
-
-    t!("1.0", 1.0);
-    t!("1.0e0", 1.0);
-    t!("1.0e+0", 1.0);
-    t!("1.0e-0", 1.0);
-    t!("1E-0", 1.0);
-    t!("1.001e-0", 1.001);
-    t!("2e10", 2e10);
-    t!("2e+10", 2e10);
-    t!("2e-10", 2e-10);
-    t!("2_0.0", 20.0);
-    t!("2_0.0_0e1_0", 20.0e10);
-    t!("2_0.1_0e1_0", 20.1e10);
-}
-
-#[test]
-fn bare_key_names() {
-    let a = "
-        foo = 3
-        foo_3 = 3
-        foo_-2--3--r23f--4-f2-4 = 3
-        _ = 3
-        - = 3
-        8 = 8
-        \"a\" = 3
-        \"!\" = 3
-        \"a^b\" = 3
-        \"\\\"\" = 3
-        \"character encoding\" = \"value\"
-        'ʎǝʞ' = \"value\"
-    "
-    .parse::<Value>()
-    .unwrap();
-    let _ = &a["foo"];
-    let _ = &a["-"];
-    let _ = &a["_"];
-    let _ = &a["8"];
-    let _ = &a["foo_3"];
-    let _ = &a["foo_-2--3--r23f--4-f2-4"];
-    let _ = &a["a"];
-    let _ = &a["!"];
-    let _ = &a["\""];
-    let _ = &a["character encoding"];
-    let _ = &a["ʎǝʞ"];
-}
-
-#[test]
-fn bad_keys() {
-    bad!(
-        "key\n=3",
-        "\
-TOML parse error at line 1, column 4
-  |
-1 | key
-  |    ^
-expected `.`, `=`
-"
-    );
-    bad!(
-        "key=\n3",
-        "\
-TOML parse error at line 1, column 5
-  |
-1 | key=
-  |     ^
-invalid string
-expected `\"`, `'`
-"
-    );
-    bad!(
-        "key|=3",
-        "\
-TOML parse error at line 1, column 4
-  |
-1 | key|=3
-  |    ^
-expected `.`, `=`
-"
-    );
-    bad!(
-        "=3",
-        "\
-TOML parse error at line 1, column 1
-  |
-1 | =3
-  | ^
-invalid key
-"
-    );
-    bad!(
-        "\"\"|=3",
-        "\
-TOML parse error at line 1, column 3
-  |
-1 | \"\"|=3
-  |   ^
-expected `.`, `=`
-"
-    );
-    bad!(
-        "\"\n\"|=3",
-        "\
-TOML parse error at line 1, column 2
-  |
-1 | \"
-  |  ^
-invalid basic string
-"
-    );
-    bad!(
-        "\"\r\"|=3",
-        "\
-TOML parse error at line 1, column 2
-  |
-1 | \"\r\"|=3
-  |  ^
-invalid basic string
-"
-    );
-    bad!(
-        "''''''=3",
-        "\
-TOML parse error at line 1, column 3
-  |
-1 | ''''''=3
-  |   ^
-expected `.`, `=`
-"
-    );
-    bad!(
-        "\"\"\"\"\"\"=3",
-        "\
-TOML parse error at line 1, column 3
-  |
-1 | \"\"\"\"\"\"=3
-  |   ^
-expected `.`, `=`
-"
-    );
-    bad!(
-        "'''key'''=3",
-        "\
-TOML parse error at line 1, column 3
-  |
-1 | '''key'''=3
-  |   ^
-expected `.`, `=`
-"
-    );
-    bad!(
-        "\"\"\"key\"\"\"=3",
-        "\
-TOML parse error at line 1, column 3
-  |
-1 | \"\"\"key\"\"\"=3
-  |   ^
-expected `.`, `=`
-"
-    );
-}
-
-#[test]
-fn bad_table_names() {
-    bad!(
-        "[]",
-        "\
-TOML parse error at line 1, column 2
-  |
-1 | []
-  |  ^
-invalid key
-"
-    );
-    bad!(
-        "[.]",
-        "\
-TOML parse error at line 1, column 2
-  |
-1 | [.]
-  |  ^
-invalid key
-"
-    );
-    bad!(
-        "[a.]",
-        "\
-TOML parse error at line 1, column 3
-  |
-1 | [a.]
-  |   ^
-invalid table header
-expected `.`, `]`
-"
-    );
-    bad!(
-        "[!]",
-        "\
-TOML parse error at line 1, column 2
-  |
-1 | [!]
-  |  ^
-invalid key
-"
-    );
-    bad!(
-        "[\"\n\"]",
-        "\
-TOML parse error at line 1, column 3
-  |
-1 | [\"
-  |   ^
-invalid basic string
-"
-    );
-    bad!(
-        "[a.b]\n[a.\"b\"]",
-        "\
-TOML parse error at line 2, column 1
-  |
-2 | [a.\"b\"]
-  | ^
-invalid table header
-duplicate key `b` in table `a`
-"
-    );
-    bad!(
-        "[']",
-        "\
-TOML parse error at line 1, column 4
-  |
-1 | [']
-  |    ^
-invalid literal string
-"
-    );
-    bad!(
-        "[''']",
-        "\
-TOML parse error at line 1, column 4
-  |
-1 | [''']
-  |    ^
-invalid table header
-expected `.`, `]`
-"
-    );
-    bad!(
-        "['''''']",
-        "\
-TOML parse error at line 1, column 4
-  |
-1 | ['''''']
-  |    ^
-invalid table header
-expected `.`, `]`
-"
-    );
-    bad!(
-        "['''foo''']",
-        "\
-TOML parse error at line 1, column 4
-  |
-1 | ['''foo''']
-  |    ^
-invalid table header
-expected `.`, `]`
-"
-    );
-    bad!(
-        "[\"\"\"bar\"\"\"]",
-        "\
-TOML parse error at line 1, column 4
-  |
-1 | [\"\"\"bar\"\"\"]
-  |    ^
-invalid table header
-expected `.`, `]`
-"
-    );
-    bad!(
-        "['\n']",
-        "\
-TOML parse error at line 1, column 3
-  |
-1 | ['
-  |   ^
-invalid literal string
-"
-    );
-    bad!(
-        "['\r\n']",
-        "\
-TOML parse error at line 1, column 3
-  |
-1 | ['
-  |   ^
-invalid literal string
-"
-    );
-}
-
-#[test]
-fn table_names() {
-    let a = "
-        [a.\"b\"]
-        [\"f f\"]
-        [\"f.f\"]
-        [\"\\\"\"]
-        ['a.a']
-        ['\"\"']
-    "
-    .parse::<Value>()
-    .unwrap();
-    println!("{:?}", a);
-    let _ = &a["a"]["b"];
-    let _ = &a["f f"];
-    let _ = &a["f.f"];
-    let _ = &a["\""];
-    let _ = &a["\"\""];
-}
-
-#[test]
-fn invalid_bare_numeral() {
-    bad!(
-        "4",
-        "\
-TOML parse error at line 1, column 2
-  |
-1 | 4
-  |  ^
-expected `.`, `=`
-"
-    );
-}
-
-#[test]
-fn inline_tables() {
-    "a = {}".parse::<Value>().unwrap();
-    "a = {b=1}".parse::<Value>().unwrap();
-    "a = {   b   =   1    }".parse::<Value>().unwrap();
-    "a = {a=1,b=2}".parse::<Value>().unwrap();
-    "a = {a=1,b=2,c={}}".parse::<Value>().unwrap();
-
-    bad!(
-        "a = {a=1,}",
-        "\
-TOML parse error at line 1, column 9
-  |
-1 | a = {a=1,}
-  |         ^
-invalid inline table
-expected `}`
-"
-    );
-    bad!(
-        "a = {,}",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = {,}
-  |      ^
-invalid inline table
-expected `}`
-"
-    );
-    bad!(
-        "a = {a=1,a=1}",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = {a=1,a=1}
-  |      ^
-duplicate key `a`
-"
-    );
-    bad!(
-        "a = {\n}",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = {
-  |      ^
-invalid inline table
-expected `}`
-"
-    );
-    bad!(
-        "a = {",
-        "\
-TOML parse error at line 1, column 6
-  |
-1 | a = {
-  |      ^
-invalid inline table
-expected `}`
-"
-    );
-
-    "a = {a=[\n]}".parse::<Value>().unwrap();
-    "a = {\"a\"=[\n]}".parse::<Value>().unwrap();
-    "a = [\n{},\n{},\n]".parse::<Value>().unwrap();
-}
-
-#[test]
-fn number_underscores() {
-    macro_rules! t {
-        ($actual:expr, $expected:expr) => {{
-            let f = format!("foo = {}", $actual);
-            let table = f.parse::<Value>().unwrap();
-            assert_eq!(table["foo"].as_integer().unwrap(), $expected);
-        }};
-    }
-
-    t!("1_0", 10);
-    t!("1_0_0", 100);
-    t!("1_000", 1000);
-    t!("+1_000", 1000);
-    t!("-1_000", -1000);
-}
-
-#[test]
-fn bad_underscores() {
-    bad!(
-        "foo = 0_",
-        "\
-TOML parse error at line 1, column 8
-  |
-1 | foo = 0_
-  |        ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "foo = 0__0",
-        "\
-TOML parse error at line 1, column 8
-  |
-1 | foo = 0__0
-  |        ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "foo = __0",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | foo = __0
-  |       ^
-invalid integer
-expected leading digit
-"
-    );
-    bad!(
-        "foo = 1_0_",
-        "\
-TOML parse error at line 1, column 11
-  |
-1 | foo = 1_0_
-  |           ^
-invalid integer
-expected digit
-"
-    );
-}
-
-#[test]
-fn bad_unicode_codepoint() {
-    bad!(
-        "foo = \"\\uD800\"",
-        "\
-TOML parse error at line 1, column 10
-  |
-1 | foo = \"\\uD800\"
-  |          ^
-invalid unicode 4-digit hex code
-value is out of range
-"
-    );
-}
-
-#[test]
-fn bad_strings() {
-    bad!(
-        "foo = \"\\uxx\"",
-        "\
-TOML parse error at line 1, column 10
-  |
-1 | foo = \"\\uxx\"
-  |          ^
-invalid unicode 4-digit hex code
-"
-    );
-    bad!(
-        "foo = \"\\u\"",
-        "\
-TOML parse error at line 1, column 10
-  |
-1 | foo = \"\\u\"
-  |          ^
-invalid unicode 4-digit hex code
-"
-    );
-    bad!(
-        "foo = \"\\",
-        "\
-TOML parse error at line 1, column 8
-  |
-1 | foo = \"\\
-  |        ^
-invalid basic string
-"
-    );
-    bad!(
-        "foo = '",
-        "\
-TOML parse error at line 1, column 8
-  |
-1 | foo = '
-  |        ^
-invalid literal string
-"
-    );
-}
-
-#[test]
-fn empty_string() {
-    assert_eq!(
-        "foo = \"\"".parse::<Value>().unwrap()["foo"]
-            .as_str()
-            .unwrap(),
-        ""
-    );
-}
-
-#[test]
-fn booleans() {
-    let table = "foo = true".parse::<Value>().unwrap();
-    assert_eq!(table["foo"].as_bool(), Some(true));
-
-    let table = "foo = false".parse::<Value>().unwrap();
-    assert_eq!(table["foo"].as_bool(), Some(false));
-
-    bad!(
-        "foo = true2",
-        "\
-TOML parse error at line 1, column 11
-  |
-1 | foo = true2
-  |           ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "foo = false2",
-        "\
-TOML parse error at line 1, column 12
-  |
-1 | foo = false2
-  |            ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "foo = t1",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | foo = t1
-  |       ^
-invalid string
-expected `\"`, `'`
-"
-    );
-    bad!(
-        "foo = f2",
-        "\
-TOML parse error at line 1, column 7
-  |
-1 | foo = f2
-  |       ^
-invalid string
-expected `\"`, `'`
-"
-    );
-}
-
-#[test]
-fn bad_nesting() {
-    bad!(
-        "
-        a = [2]
-        [[a]]
-        b = 5
-        ",
-        "\
-TOML parse error at line 3, column 9
-  |
-3 |         [[a]]
-  |         ^
-invalid table header
-duplicate key `a` in document root
-"
-    );
-    bad!(
-        "
-        a = 1
-        [a.b]
-        ",
-        "\
-TOML parse error at line 3, column 9
-  |
-3 |         [a.b]
-  |         ^
-invalid table header
-dotted key `a` attempted to extend non-table type (integer)
-"
-    );
-    bad!(
-        "
-        a = []
-        [a.b]
-        ",
-        "\
-TOML parse error at line 3, column 9
-  |
-3 |         [a.b]
-  |         ^
-invalid table header
-dotted key `a` attempted to extend non-table type (array)
-"
-    );
-    bad!(
-        "
-        a = []
-        [[a.b]]
-        ",
-        "\
-TOML parse error at line 3, column 9
-  |
-3 |         [[a.b]]
-  |         ^
-invalid table header
-dotted key `a` attempted to extend non-table type (array)
-"
-    );
-    bad!(
-        "
-        [a]
-        b = { c = 2, d = {} }
-        [a.b]
-        c = 2
-        ",
-        "\
-TOML parse error at line 4, column 9
-  |
-4 |         [a.b]
-  |         ^
-invalid table header
-duplicate key `b` in table `a`
-"
-    );
-}
-
-#[test]
-fn bad_table_redefine() {
-    bad!(
-        "
-        [a]
-        foo=\"bar\"
-        [a.b]
-        foo=\"bar\"
-        [a]
-        ",
-        "\
-TOML parse error at line 6, column 9
-  |
-6 |         [a]
-  |         ^
-invalid table header
-duplicate key `a` in document root
-"
-    );
-    bad!(
-        "
-        [a]
-        foo=\"bar\"
-        b = { foo = \"bar\" }
-        [a]
-        ",
-        "\
-TOML parse error at line 5, column 9
-  |
-5 |         [a]
-  |         ^
-invalid table header
-duplicate key `a` in document root
-"
-    );
-    bad!(
-        "
-        [a]
-        b = {}
-        [a.b]
-        ",
-        "\
-TOML parse error at line 4, column 9
-  |
-4 |         [a.b]
-  |         ^
-invalid table header
-duplicate key `b` in table `a`
-"
-    );
-
-    bad!(
-        "
-        [a]
-        b = {}
-        [a]
-        ",
-        "\
-TOML parse error at line 4, column 9
-  |
-4 |         [a]
-  |         ^
-invalid table header
-duplicate key `a` in document root
-"
-    );
-}
-
-#[test]
-fn datetimes() {
-    macro_rules! t {
-        ($actual:expr) => {{
-            let f = format!("foo = {}", $actual);
-            let toml = f.parse::<Value>().expect(&format!("failed: {}", f));
-            assert_eq!(toml["foo"].as_datetime().unwrap().to_string(), $actual);
-        }};
-    }
-
-    t!("2016-09-09T09:09:09Z");
-    t!("2016-09-09T09:09:09.1Z");
-    t!("2016-09-09T09:09:09.2+10:00");
-    t!("2016-09-09T09:09:09.123456789-02:00");
-    bad!(
-        "foo = 2016-09-09T09:09:09.Z",
-        "\
-TOML parse error at line 1, column 26
-  |
-1 | foo = 2016-09-09T09:09:09.Z
-  |                          ^
-expected newline, `#`
-"
-    );
-    bad!(
-        "foo = 2016-9-09T09:09:09Z",
-        "\
-TOML parse error at line 1, column 12
-  |
-1 | foo = 2016-9-09T09:09:09Z
-  |            ^
-invalid date-time
-"
-    );
-    bad!(
-        "foo = 2016-09-09T09:09:09+2:00",
-        "\
-TOML parse error at line 1, column 27
-  |
-1 | foo = 2016-09-09T09:09:09+2:00
-  |                           ^
-invalid time offset
-"
-    );
-    bad!(
-        "foo = 2016-09-09T09:09:09-2:00",
-        "\
-TOML parse error at line 1, column 27
-  |
-1 | foo = 2016-09-09T09:09:09-2:00
-  |                           ^
-invalid time offset
-"
-    );
-    bad!(
-        "foo = 2016-09-09T09:09:09Z-2:00",
-        "\
-TOML parse error at line 1, column 27
-  |
-1 | foo = 2016-09-09T09:09:09Z-2:00
-  |                           ^
-expected newline, `#`
-"
-    );
-}
-
-#[test]
-fn require_newline_after_value() {
-    bad!(
-        "0=0r=false",
-        "\
-TOML parse error at line 1, column 4
-  |
-1 | 0=0r=false
-  |    ^
-expected newline, `#`
-"
-    );
-    bad!(
-        r#"
-0=""o=""m=""r=""00="0"q="""0"""e="""0"""
-"#,
-        r#"TOML parse error at line 2, column 5
-  |
-2 | 0=""o=""m=""r=""00="0"q="""0"""e="""0"""
-  |     ^
-expected newline, `#`
-"#
-    );
-    bad!(
-        r#"
-[[0000l0]]
-0="0"[[0000l0]]
-0="0"[[0000l0]]
-0="0"l="0"
-"#,
-        r#"TOML parse error at line 3, column 6
-  |
-3 | 0="0"[[0000l0]]
-  |      ^
-expected newline, `#`
-"#
-    );
-    bad!(
-        r#"
-0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
-"#,
-        r#"TOML parse error at line 2, column 6
-  |
-2 | 0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
-  |      ^
-expected newline, `#`
-"#
-    );
-    bad!(
-        r#"
-0=0r0=0r=false
-"#,
-        r#"TOML parse error at line 2, column 4
-  |
-2 | 0=0r0=0r=false
-  |    ^
-expected newline, `#`
-"#
-    );
-    bad!(
-        r#"
-0=0r0=0r=falsefal=false
-"#,
-        r#"TOML parse error at line 2, column 4
-  |
-2 | 0=0r0=0r=falsefal=false
-  |    ^
-expected newline, `#`
-"#
-    );
-}
diff --git a/crates/toml/tests/testsuite/pretty.rs b/crates/toml/tests/testsuite/pretty.rs
index 0b06fa2b..3ae772b4 100644
--- a/crates/toml/tests/testsuite/pretty.rs
+++ b/crates/toml/tests/testsuite/pretty.rs
@@ -64,7 +64,7 @@ single = "this is a single line but has '' cuz it's tricky"
 single_tricky = "single line with ''' in it"
 tabs = """
 this is pretty standard
-\texcept for some   \btabs right here
+\texcept for some   \ttabs right here
 """
 text = """
 this is the first line.
@@ -130,3 +130,55 @@ fn table_array() {
     value.serialize(toml::Serializer::new(&mut result)).unwrap();
     assert_eq(toml, &result);
 }
+
+const PRETTY_EMPTY_TABLE: &str = r#"[example]
+"#;
+
+#[test]
+fn pretty_empty_table() {
+    let toml = PRETTY_EMPTY_TABLE;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    value.serialize(toml::Serializer::new(&mut result)).unwrap();
+    assert_eq(toml, &result);
+}
+
+#[test]
+fn error_includes_key() {
+    #[derive(Debug, serde::Serialize, serde::Deserialize)]
+    struct Package {
+        name: String,
+        version: String,
+        authors: Vec<String>,
+        profile: Profile,
+    }
+
+    #[derive(Debug, serde::Serialize, serde::Deserialize)]
+    struct Profile {
+        dev: Dev,
+    }
+
+    #[derive(Debug, serde::Serialize, serde::Deserialize)]
+    struct Dev {
+        debug: U32OrBool,
+    }
+
+    #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq)]
+    #[serde(untagged, expecting = "expected a boolean or an integer")]
+    pub enum U32OrBool {
+        U32(u32),
+        Bool(bool),
+    }
+
+    let raw = r#"name = "foo"
+version = "0.0.0"
+authors = []
+
+[profile.dev]
+debug = true
+"#;
+
+    let pkg: Package = toml::from_str(raw).unwrap();
+    let pretty = toml::to_string_pretty(&pkg).unwrap();
+    assert_eq(raw, pretty);
+}
diff --git a/crates/toml/tests/testsuite/serde.rs b/crates/toml/tests/testsuite/serde.rs
index a9dd3e5a..f88dd106 100644
--- a/crates/toml/tests/testsuite/serde.rs
+++ b/crates/toml/tests/testsuite/serde.rs
@@ -376,6 +376,26 @@ fn parse_enum_string() {
     }
 }
 
+#[test]
+#[cfg(feature = "preserve_order")]
+fn map_key_unit_variants() {
+    #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, PartialOrd, Ord)]
+    enum Sort {
+        #[serde(rename = "ascending")]
+        Asc,
+        Desc,
+    }
+
+    let mut map = BTreeMap::new();
+    map.insert(Sort::Asc, 1);
+    map.insert(Sort::Desc, 2);
+
+    equivalent! {
+        map,
+        map! { ascending: Value::Integer(1), Desc: Value::Integer(2) },
+    }
+}
+
 // #[test]
 // fn unused_fields() {
 //     #[derive(Serialize, Deserialize, PartialEq, Debug)]
@@ -694,6 +714,295 @@ fn json_interoperability() {
     .unwrap();
 }
 
+#[test]
+fn error_includes_key() {
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Package {
+        name: String,
+        version: String,
+        authors: Vec<String>,
+        profile: Profile,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Profile {
+        dev: Dev,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Dev {
+        debug: U32OrBool,
+    }
+
+    #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
+    #[serde(untagged, expecting = "expected a boolean or an integer")]
+    pub enum U32OrBool {
+        U32(u32),
+        Bool(bool),
+    }
+
+    let res: Result<Package, _> = toml::from_str(
+        r#"
+[package]
+name = "foo"
+version = "0.0.0"
+authors = []
+
+[profile.dev]
+debug = 'a'
+"#,
+    );
+    let err = res.unwrap_err();
+    snapbox::assert_eq(
+        r#"TOML parse error at line 8, column 9
+  |
+8 | debug = 'a'
+  |         ^^^
+expected a boolean or an integer
+"#,
+        err.to_string(),
+    );
+
+    let res: Result<Package, _> = toml::from_str(
+        r#"
+[package]
+name = "foo"
+version = "0.0.0"
+authors = []
+
+[profile]
+dev = { debug = 'a' }
+"#,
+    );
+    let err = res.unwrap_err();
+    snapbox::assert_eq(
+        r#"TOML parse error at line 8, column 17
+  |
+8 | dev = { debug = 'a' }
+  |                 ^^^
+expected a boolean or an integer
+"#,
+        err.to_string(),
+    );
+}
+
+#[test]
+fn newline_key_value() {
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Package {
+        name: String,
+    }
+
+    let package = Package {
+        name: "foo".to_owned(),
+    };
+    let raw = toml::to_string_pretty(&package).unwrap();
+    snapbox::assert_eq(
+        r#"name = "foo"
+"#,
+        raw,
+    );
+}
+
+#[test]
+fn newline_table() {
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Manifest {
+        package: Package,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Package {
+        name: String,
+    }
+
+    let package = Manifest {
+        package: Package {
+            name: "foo".to_owned(),
+        },
+    };
+    let raw = toml::to_string_pretty(&package).unwrap();
+    snapbox::assert_eq(
+        r#"[package]
+name = "foo"
+"#,
+        raw,
+    );
+}
+
+#[test]
+fn newline_dotted_table() {
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Manifest {
+        profile: Profile,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Profile {
+        dev: Dev,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Dev {
+        debug: U32OrBool,
+    }
+
+    #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
+    #[serde(untagged, expecting = "expected a boolean or an integer")]
+    pub enum U32OrBool {
+        U32(u32),
+        Bool(bool),
+    }
+
+    let package = Manifest {
+        profile: Profile {
+            dev: Dev {
+                debug: U32OrBool::Bool(true),
+            },
+        },
+    };
+    let raw = toml::to_string_pretty(&package).unwrap();
+    snapbox::assert_eq(
+        r#"[profile.dev]
+debug = true
+"#,
+        raw,
+    );
+}
+
+#[test]
+fn newline_mixed_tables() {
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Manifest {
+        cargo_features: Vec<String>,
+        package: Package,
+        profile: Profile,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Package {
+        name: String,
+        version: String,
+        authors: Vec<String>,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Profile {
+        dev: Dev,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Dev {
+        debug: U32OrBool,
+    }
+
+    #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
+    #[serde(untagged, expecting = "expected a boolean or an integer")]
+    pub enum U32OrBool {
+        U32(u32),
+        Bool(bool),
+    }
+
+    let package = Manifest {
+        cargo_features: vec![],
+        package: Package {
+            name: "foo".to_owned(),
+            version: "1.0.0".to_owned(),
+            authors: vec![],
+        },
+        profile: Profile {
+            dev: Dev {
+                debug: U32OrBool::Bool(true),
+            },
+        },
+    };
+    let raw = toml::to_string_pretty(&package).unwrap();
+    snapbox::assert_eq(
+        r#"cargo_features = []
+
+[package]
+name = "foo"
+version = "1.0.0"
+authors = []
+
+[profile.dev]
+debug = true
+"#,
+        raw,
+    );
+}
+
+#[test]
+fn integer_min() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a_b: i64,
+    }
+
+    equivalent! {
+        Foo { a_b: i64::MIN },
+        map! { a_b: Value::Integer(i64::MIN) },
+    }
+}
+
+#[test]
+fn integer_max() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a_b: i64,
+    }
+
+    equivalent! {
+        Foo { a_b: i64::MAX },
+        map! { a_b: Value::Integer(i64::MAX) },
+    }
+}
+
+#[test]
+fn float_min() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a_b: f64,
+    }
+
+    equivalent! {
+        Foo { a_b: f64::MIN },
+        map! { a_b: Value::Float(f64::MIN) },
+    }
+}
+
+#[test]
+fn float_max() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a_b: f64,
+    }
+
+    equivalent! {
+        Foo { a_b: f64::MAX },
+        map! { a_b: Value::Float(f64::MAX) },
+    }
+}
+
+#[test]
+fn unsupported_root_type() {
+    let native = "value";
+    let err = toml::to_string_pretty(&native).unwrap_err();
+    snapbox::assert_eq("unsupported rust type", err.to_string());
+}
+
+#[test]
+fn unsupported_nested_type() {
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Foo {
+        unused: (),
+    }
+
+    let native = Foo { unused: () };
+    let err = toml::to_string_pretty(&native).unwrap_err();
+    snapbox::assert_eq("unsupported unit type", err.to_string());
+}
+
 #[test]
 fn table_type_enum_regression_issue_388() {
     #[derive(Deserialize)]
diff --git a/crates/toml_edit/tests/easy_decoder.rs b/crates/toml_edit/tests/easy_decoder.rs
deleted file mode 100644
index 9395d4eb..00000000
--- a/crates/toml_edit/tests/easy_decoder.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-#![cfg(feature = "easy")]
-
-#[derive(Copy, Clone)]
-pub struct Decoder;
-
-impl toml_test_harness::Decoder for Decoder {
-    fn name(&self) -> &str {
-        "toml_edit::easy"
-    }
-
-    fn decode(&self, data: &[u8]) -> Result<toml_test_harness::Decoded, toml_test_harness::Error> {
-        let data = std::str::from_utf8(data).map_err(toml_test_harness::Error::new)?;
-        let document = data
-            .parse::<toml_edit::easy::Value>()
-            .map_err(toml_test_harness::Error::new)?;
-        value_to_decoded(&document)
-    }
-}
-
-fn value_to_decoded(
-    value: &toml_edit::easy::Value,
-) -> Result<toml_test_harness::Decoded, toml_test_harness::Error> {
-    match value {
-        toml_edit::easy::Value::Integer(v) => Ok(toml_test_harness::Decoded::Value(
-            toml_test_harness::DecodedValue::from(*v),
-        )),
-        toml_edit::easy::Value::String(v) => Ok(toml_test_harness::Decoded::Value(
-            toml_test_harness::DecodedValue::from(v),
-        )),
-        toml_edit::easy::Value::Float(v) => Ok(toml_test_harness::Decoded::Value(
-            toml_test_harness::DecodedValue::from(*v),
-        )),
-        toml_edit::easy::Value::Datetime(v) => {
-            let value = v.to_string();
-            let value = match (v.date.is_some(), v.time.is_some(), v.offset.is_some()) {
-                (true, true, true) => toml_test_harness::DecodedValue::Datetime(value),
-                (true, true, false) => toml_test_harness::DecodedValue::DatetimeLocal(value),
-                (true, false, false) => toml_test_harness::DecodedValue::DateLocal(value),
-                (false, true, false) => toml_test_harness::DecodedValue::TimeLocal(value),
-                _ => unreachable!("Unsupported case"),
-            };
-            Ok(toml_test_harness::Decoded::Value(value))
-        }
-        toml_edit::easy::Value::Boolean(v) => Ok(toml_test_harness::Decoded::Value(
-            toml_test_harness::DecodedValue::from(*v),
-        )),
-        toml_edit::easy::Value::Array(v) => {
-            let v: Result<_, toml_test_harness::Error> = v.iter().map(value_to_decoded).collect();
-            Ok(toml_test_harness::Decoded::Array(v?))
-        }
-        toml_edit::easy::Value::Table(v) => table_to_decoded(v),
-    }
-}
-
-fn table_to_decoded(
-    value: &toml_edit::easy::value::Table,
-) -> Result<toml_test_harness::Decoded, toml_test_harness::Error> {
-    let table: Result<_, toml_test_harness::Error> = value
-        .iter()
-        .map(|(k, v)| {
-            let k = k.to_owned();
-            let v = value_to_decoded(v)?;
-            Ok((k, v))
-        })
-        .collect();
-    Ok(toml_test_harness::Decoded::Table(table?))
-}
diff --git a/crates/toml_edit/tests/easy_decoder_compliance.rs b/crates/toml_edit/tests/easy_decoder_compliance.rs
deleted file mode 100644
index e237aa77..00000000
--- a/crates/toml_edit/tests/easy_decoder_compliance.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-#[cfg(feature = "easy")]
-mod easy_decoder;
-
-#[cfg(feature = "easy")]
-fn main() {
-    let decoder = easy_decoder::Decoder;
-    let mut harness = toml_test_harness::DecoderHarness::new(decoder);
-    harness
-        .ignore([
-            "valid/spec/float-0.toml",
-            // Unreleased
-            "valid/string/escape-esc.toml",
-            "valid/string/hex-escape.toml",
-            "valid/datetime/no-seconds.toml",
-            "valid/inline-table/newline.toml",
-        ])
-        .unwrap();
-    harness.test();
-}
-
-#[cfg(not(feature = "easy"))]
-fn main() {}
diff --git a/crates/toml_edit/tests/easy_encoder.rs b/crates/toml_edit/tests/easy_encoder.rs
deleted file mode 100644
index 35b5beb3..00000000
--- a/crates/toml_edit/tests/easy_encoder.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-#![cfg(feature = "easy")]
-
-#[derive(Copy, Clone)]
-pub struct Encoder;
-
-impl toml_test_harness::Encoder for Encoder {
-    fn name(&self) -> &str {
-        "toml_edit::easy"
-    }
-
-    fn encode(&self, data: toml_test_harness::Decoded) -> Result<String, toml_test_harness::Error> {
-        let value = from_decoded(&data)?;
-        let s = toml_edit::easy::to_string(&value).map_err(toml_test_harness::Error::new)?;
-        Ok(s)
-    }
-}
-
-fn from_decoded(
-    decoded: &toml_test_harness::Decoded,
-) -> Result<toml_edit::easy::Value, toml_test_harness::Error> {
-    let value = match decoded {
-        toml_test_harness::Decoded::Value(value) => from_decoded_value(value)?,
-        toml_test_harness::Decoded::Table(value) => {
-            toml_edit::easy::Value::Table(from_table(value)?)
-        }
-        toml_test_harness::Decoded::Array(value) => {
-            toml_edit::easy::Value::Array(from_array(value)?)
-        }
-    };
-    Ok(value)
-}
-
-fn from_decoded_value(
-    decoded: &toml_test_harness::DecodedValue,
-) -> Result<toml_edit::easy::Value, toml_test_harness::Error> {
-    match decoded {
-        toml_test_harness::DecodedValue::String(value) => {
-            Ok(toml_edit::easy::Value::String(value.clone()))
-        }
-        toml_test_harness::DecodedValue::Integer(value) => value
-            .parse::<i64>()
-            .map_err(toml_test_harness::Error::new)
-            .map(toml_edit::easy::Value::Integer),
-        toml_test_harness::DecodedValue::Float(value) => value
-            .parse::<f64>()
-            .map_err(toml_test_harness::Error::new)
-            .map(toml_edit::easy::Value::Float),
-        toml_test_harness::DecodedValue::Bool(value) => value
-            .parse::<bool>()
-            .map_err(toml_test_harness::Error::new)
-            .map(toml_edit::easy::Value::Boolean),
-        toml_test_harness::DecodedValue::Datetime(value) => value
-            .parse::<toml_edit::Datetime>()
-            .map_err(toml_test_harness::Error::new)
-            .map(toml_edit::easy::Value::Datetime),
-        toml_test_harness::DecodedValue::DatetimeLocal(value) => value
-            .parse::<toml_edit::Datetime>()
-            .map_err(toml_test_harness::Error::new)
-            .map(toml_edit::easy::Value::Datetime),
-        toml_test_harness::DecodedValue::DateLocal(value) => value
-            .parse::<toml_edit::Datetime>()
-            .map_err(toml_test_harness::Error::new)
-            .map(toml_edit::easy::Value::Datetime),
-        toml_test_harness::DecodedValue::TimeLocal(value) => value
-            .parse::<toml_edit::Datetime>()
-            .map_err(toml_test_harness::Error::new)
-            .map(toml_edit::easy::Value::Datetime),
-    }
-}
-
-fn from_table(
-    decoded: &std::collections::HashMap<String, toml_test_harness::Decoded>,
-) -> Result<toml_edit::easy::value::Table, toml_test_harness::Error> {
-    decoded
-        .iter()
-        .map(|(k, v)| {
-            let v = from_decoded(v)?;
-            Ok((k.to_owned(), v))
-        })
-        .collect()
-}
-
-fn from_array(
-    decoded: &[toml_test_harness::Decoded],
-) -> Result<toml_edit::easy::value::Array, toml_test_harness::Error> {
-    decoded.iter().map(from_decoded).collect()
-}
diff --git a/crates/toml_edit/tests/easy_encoder_compliance.rs b/crates/toml_edit/tests/easy_encoder_compliance.rs
deleted file mode 100644
index a53527f8..00000000
--- a/crates/toml_edit/tests/easy_encoder_compliance.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-#[cfg(feature = "easy")]
-mod easy_decoder;
-#[cfg(feature = "easy")]
-mod easy_encoder;
-
-#[cfg(feature = "easy")]
-fn main() {
-    let encoder = easy_encoder::Encoder;
-    let decoder = easy_decoder::Decoder;
-    let mut harness = toml_test_harness::EncoderHarness::new(encoder, decoder);
-    harness.ignore(["valid/spec/float-0.toml"]).unwrap();
-    harness.test();
-}
-
-#[cfg(not(feature = "easy"))]
-fn main() {}
diff --git a/crates/toml/tests/testsuite/datetime.rs b/crates/toml_edit/tests/testsuite/datetime.rs
similarity index 95%
rename from crates/toml/tests/testsuite/datetime.rs
rename to crates/toml_edit/tests/testsuite/datetime.rs
index ecb2bdd0..541f8ea1 100644
--- a/crates/toml/tests/testsuite/datetime.rs
+++ b/crates/toml_edit/tests/testsuite/datetime.rs
@@ -1,8 +1,6 @@
-use std::str::FromStr;
-
 macro_rules! bad {
     ($toml:expr, $msg:expr) => {
-        match $toml.parse::<toml::Value>() {
+        match $toml.parse::<toml_edit::Document>() {
             Ok(s) => panic!("parsed to: {:#?}", s),
             Err(e) => snapbox::assert_eq($msg, e.to_string()),
         }
@@ -13,8 +11,11 @@ macro_rules! bad {
 fn times() {
     fn dogood(s: &str, serialized: &str) {
         let to_parse = format!("foo = {}", s);
-        let value = toml::Value::from_str(&to_parse).unwrap();
-        assert_eq!(value["foo"].as_datetime().unwrap().to_string(), serialized);
+        let document = to_parse.parse::<toml_edit::Document>().unwrap();
+        assert_eq!(
+            document["foo"].as_datetime().unwrap().to_string(),
+            serialized
+        );
     }
     fn good(s: &str) {
         dogood(s, s);
diff --git a/crates/toml_edit/tests/testsuite/enum_external_deserialize.rs b/crates/toml_edit/tests/testsuite/enum_external_deserialize.rs
deleted file mode 100644
index e136a28b..00000000
--- a/crates/toml_edit/tests/testsuite/enum_external_deserialize.rs
+++ /dev/null
@@ -1,279 +0,0 @@
-#![cfg(feature = "easy")]
-
-use serde::Deserialize;
-use toml_edit::easy as toml;
-
-#[derive(Debug, Deserialize, PartialEq)]
-struct OuterStruct {
-    inner: TheEnum,
-}
-
-#[derive(Debug, Deserialize, PartialEq)]
-enum TheEnum {
-    Plain,
-    Tuple(i64, bool),
-    NewType(String),
-    Struct { value: i64 },
-}
-
-#[derive(Debug, Deserialize, PartialEq)]
-struct Val {
-    val: TheEnum,
-}
-
-#[derive(Debug, Deserialize, PartialEq)]
-struct Multi {
-    enums: Vec<TheEnum>,
-}
-
-#[test]
-fn invalid_variant_returns_error_with_good_message_string() {
-    let error = toml::from_str::<Val>("val = \"NonExistent\"").unwrap_err();
-
-    snapbox::assert_eq(
-        r#"TOML parse error at line 1, column 7
-  |
-1 | val = "NonExistent"
-  |       ^^^^^^^^^^^^^
-unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct`
-"#,
-        error.to_string(),
-    );
-}
-
-#[test]
-fn invalid_variant_returns_error_with_good_message_inline_table() {
-    let error = toml::from_str::<Val>("val = { NonExistent = {} }").unwrap_err();
-    snapbox::assert_eq(
-        r#"TOML parse error at line 1, column 9
-  |
-1 | val = { NonExistent = {} }
-  |         ^^^^^^^^^^^
-unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct`
-"#,
-        error.to_string(),
-    );
-}
-
-#[test]
-fn extra_field_returns_expected_empty_table_error() {
-    let error = toml::from_str::<Val>("val = { Plain = { extra_field = 404 } }").unwrap_err();
-
-    snapbox::assert_eq(
-        r#"TOML parse error at line 1, column 17
-  |
-1 | val = { Plain = { extra_field = 404 } }
-  |                 ^^^^^^^^^^^^^^^^^^^^^
-expected empty table
-"#,
-        error.to_string(),
-    );
-}
-
-#[test]
-fn extra_field_returns_expected_empty_table_error_struct_variant() {
-    let error =
-        toml::from_str::<Val>("val = { Struct = { value = 123, extra_0 = 0, extra_1 = 1 } }")
-            .unwrap_err();
-
-    snapbox::assert_eq(
-        r#"TOML parse error at line 1, column 33
-  |
-1 | val = { Struct = { value = 123, extra_0 = 0, extra_1 = 1 } }
-  |                                 ^^^^^^^
-unexpected keys in table: extra_0, extra_1, available keys: value
-"#,
-        error.to_string(),
-    );
-}
-
-mod enum_unit {
-    use super::*;
-
-    #[test]
-    fn from_str() {
-        assert_eq!(
-            Val {
-                val: TheEnum::Plain
-            },
-            toml::from_str("val = \"Plain\"").unwrap()
-        );
-    }
-
-    #[test]
-    fn from_inline_table() {
-        assert_eq!(
-            Val {
-                val: TheEnum::Plain
-            },
-            toml::from_str("val = { Plain = {} }").unwrap()
-        );
-        assert_eq!(
-            Val {
-                val: TheEnum::Plain
-            },
-            toml::from_str("val = { Plain = {} }").unwrap()
-        );
-    }
-
-    #[test]
-    fn from_std_table() {
-        assert_eq!(TheEnum::Plain, toml::from_str("[Plain]\n").unwrap());
-    }
-}
-
-mod enum_tuple {
-    use super::*;
-
-    #[test]
-    fn from_inline_table() {
-        assert_eq!(
-            Val {
-                val: TheEnum::Tuple(-123, true)
-            },
-            toml::from_str("val = { Tuple = { 0 = -123, 1 = true } }").unwrap()
-        );
-    }
-
-    #[test]
-    fn from_std_table() {
-        assert_eq!(
-            TheEnum::Tuple(-123, true),
-            toml::from_str(
-                r#"[Tuple]
-                0 = -123
-                1 = true
-                "#
-            )
-            .unwrap()
-        );
-    }
-}
-
-mod enum_newtype {
-    use super::*;
-
-    #[test]
-    fn from_inline_table() {
-        assert_eq!(
-            Val {
-                val: TheEnum::NewType("value".to_string()),
-            },
-            toml::from_str(r#"val = { NewType = "value" }"#).unwrap()
-        );
-    }
-
-    #[test]
-    fn from_std_table() {
-        assert_eq!(
-            TheEnum::NewType("value".to_string()),
-            toml::from_str(r#"NewType = "value""#).unwrap()
-        );
-        assert_eq!(
-            Val {
-                val: TheEnum::NewType("value".to_string()),
-            },
-            toml::from_str(
-                r#"[val]
-                NewType = "value"
-                "#
-            )
-            .unwrap()
-        );
-    }
-}
-
-mod enum_struct {
-    use super::*;
-
-    #[test]
-    fn from_inline_table() {
-        assert_eq!(
-            Val {
-                val: TheEnum::Struct { value: -123 }
-            },
-            toml::from_str("val = { Struct = { value = -123 } }").unwrap()
-        );
-    }
-
-    #[test]
-    fn from_std_table() {
-        assert_eq!(
-            TheEnum::Struct { value: -123 },
-            toml::from_str(
-                r#"[Struct]
-                value = -123
-                "#
-            )
-            .unwrap()
-        );
-    }
-
-    #[test]
-    fn from_nested_std_table() {
-        assert_eq!(
-            OuterStruct {
-                inner: TheEnum::Struct { value: -123 }
-            },
-            toml::from_str(
-                r#"[inner.Struct]
-                value = -123
-                "#
-            )
-            .unwrap()
-        );
-    }
-}
-
-mod enum_array {
-    use super::*;
-
-    #[test]
-    fn from_inline_tables() {
-        let toml_str = r#"
-            enums = [
-                { Plain = {} },
-                { Tuple = { 0 = -123, 1 = true } },
-                { NewType = "value" },
-                { Struct = { value = -123 } }
-            ]"#;
-        assert_eq!(
-            Multi {
-                enums: vec![
-                    TheEnum::Plain,
-                    TheEnum::Tuple(-123, true),
-                    TheEnum::NewType("value".to_string()),
-                    TheEnum::Struct { value: -123 },
-                ]
-            },
-            toml::from_str(toml_str).unwrap()
-        );
-    }
-
-    #[test]
-    fn from_std_table() {
-        let toml_str = r#"[[enums]]
-            Plain = {}
-
-            [[enums]]
-            Tuple = { 0 = -123, 1 = true }
-
-            [[enums]]
-            NewType = "value"
-
-            [[enums]]
-            Struct = { value = -123 }
-            "#;
-        assert_eq!(
-            Multi {
-                enums: vec![
-                    TheEnum::Plain,
-                    TheEnum::Tuple(-123, true),
-                    TheEnum::NewType("value".to_string()),
-                    TheEnum::Struct { value: -123 },
-                ]
-            },
-            toml::from_str(toml_str).unwrap()
-        );
-    }
-}
diff --git a/crates/toml_edit/tests/testsuite/formatter.rs b/crates/toml_edit/tests/testsuite/formatter.rs
deleted file mode 100644
index 355bd105..00000000
--- a/crates/toml_edit/tests/testsuite/formatter.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-#![cfg(feature = "easy")]
-
-use serde::{Deserialize, Serialize};
-use toml_edit::ser::to_string;
-
-#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
-struct User {
-    pub name: String,
-    pub surname: String,
-}
-
-#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
-struct Users {
-    pub user: Vec<User>,
-}
-
-#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
-struct TwoUsers {
-    pub user0: User,
-    pub user1: User,
-}
-
-#[test]
-fn no_unnecessary_newlines_array() {
-    let toml = to_string(&Users {
-        user: vec![
-            User {
-                name: "John".to_string(),
-                surname: "Doe".to_string(),
-            },
-            User {
-                name: "Jane".to_string(),
-                surname: "Dough".to_string(),
-            },
-        ],
-    })
-    .unwrap();
-    assert!(!toml.starts_with('\n'));
-}
-
-#[test]
-fn no_unnecessary_newlines_table() {
-    let toml = to_string(&TwoUsers {
-        user0: User {
-            name: "John".to_string(),
-            surname: "Doe".to_string(),
-        },
-        user1: User {
-            name: "Jane".to_string(),
-            surname: "Dough".to_string(),
-        },
-    })
-    .unwrap();
-    assert!(!toml.starts_with('\n'));
-}
diff --git a/crates/toml_edit/tests/testsuite/invalid.rs b/crates/toml_edit/tests/testsuite/invalid.rs
index f3bb1ab8..8627d1ee 100644
--- a/crates/toml_edit/tests/testsuite/invalid.rs
+++ b/crates/toml_edit/tests/testsuite/invalid.rs
@@ -42,3 +42,144 @@ expected `]`
         err.to_string(),
     );
 }
+
+#[test]
+fn bad() {
+    let toml_input = "a = 01";
+    let expected_err = "\
+TOML parse error at line 1, column 6
+  |
+1 | a = 01
+  |      ^
+expected newline, `#`
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    let toml_input = "a = 1__1";
+    let expected_err = "\
+TOML parse error at line 1, column 7
+  |
+1 | a = 1__1
+  |       ^
+invalid integer
+expected digit
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    let toml_input = "a = 1_";
+    let expected_err = "\
+TOML parse error at line 1, column 7
+  |
+1 | a = 1_
+  |       ^
+invalid integer
+expected digit
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    let toml_input = "''";
+    let expected_err = "\
+TOML parse error at line 1, column 3
+  |
+1 | ''
+  |   ^
+expected `.`, `=`
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    let toml_input = "a = 9e99999";
+    let expected_err = "\
+TOML parse error at line 1, column 5
+  |
+1 | a = 9e99999
+  |     ^
+invalid floating-point number
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    let toml_input = "a = \"\u{7f}\"";
+    let expected_err = "\
+TOML parse error at line 1, column 6
+  |
+1 | a = \"\u{7f}\"
+  |      ^
+invalid basic string
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    let toml_input = "a = '\u{7f}'";
+    let expected_err = "\
+TOML parse error at line 1, column 6
+  |
+1 | a = '\u{7f}'
+  |      ^
+invalid literal string
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    let toml_input = "a = -0x1";
+    let expected_err = "\
+TOML parse error at line 1, column 7
+  |
+1 | a = -0x1
+  |       ^
+expected newline, `#`
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    let toml_input = "a = 0x-1";
+    let expected_err = "\
+TOML parse error at line 1, column 7
+  |
+1 | a = 0x-1
+  |       ^
+invalid hexadecimal integer
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    // Dotted keys.
+    let toml_input = "a.b.c = 1
+         a.b = 2
+        ";
+    let expected_err = "\
+TOML parse error at line 2, column 10
+  |
+2 |          a.b = 2
+  |          ^
+duplicate key `b` in document root
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    let toml_input = "a = 1
+         a.b = 2";
+    let expected_err = "\
+TOML parse error at line 2, column 10
+  |
+2 |          a.b = 2
+  |          ^
+dotted key `a` attempted to extend non-table type (integer)
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+
+    let toml_input = "a = {k1 = 1, k1.name = \"joe\"}";
+    let expected_err = "\
+TOML parse error at line 1, column 6
+  |
+1 | a = {k1 = 1, k1.name = \"joe\"}
+  |      ^
+dotted key `k1` attempted to extend non-table type (integer)
+";
+    let err = toml_input.parse::<toml_edit::Document>().unwrap_err();
+    snapbox::assert_eq(expected_err, err.to_string());
+}
diff --git a/crates/toml_edit/tests/testsuite/macros.rs b/crates/toml_edit/tests/testsuite/macros.rs
deleted file mode 100644
index 8f87c661..00000000
--- a/crates/toml_edit/tests/testsuite/macros.rs
+++ /dev/null
@@ -1,370 +0,0 @@
-#![cfg(feature = "easy")]
-
-use std::f64;
-
-macro_rules! table {
-    ($($key:expr => $value:expr,)*) => {{
-        // https://github.com/rust-lang/rust/issues/60643
-        #[allow(unused_mut)]
-        let mut table = toml_edit::easy::value::Table::new();
-        $(
-            table.insert($key.to_string(), $value.into());
-        )*
-        toml_edit::easy::Value::Table(table)
-    }};
-}
-
-macro_rules! array {
-    ($($element:expr,)*) => {{
-        #![allow(clippy::vec_init_then_push)]
-
-        // https://github.com/rust-lang/rust/issues/60643
-        #[allow(unused_mut)]
-        let mut array = toml_edit::easy::value::Array::new();
-        $(
-            array.push($element.into());
-        )*
-        toml_edit::easy::Value::Array(array)
-    }};
-}
-
-macro_rules! datetime {
-    ($s:tt) => {
-        $s.parse::<toml_edit::easy::value::Datetime>().unwrap()
-    };
-}
-
-#[test]
-fn test_cargo_toml() {
-    // Simple sanity check of:
-    //
-    //   - Ordinary tables
-    //   - Inline tables
-    //   - Inline arrays
-    //   - String values
-    //   - Table keys containing hyphen
-    //   - Table headers containing hyphen
-    let actual = toml_edit::easy::toml! {
-        [package]
-        name = "toml"
-        version = "0.4.5"
-        authors = ["Alex Crichton <alex@alexcrichton.com>"]
-
-        [badges]
-        travis-ci = { repository = "alexcrichton/toml-rs" }
-
-        [dependencies]
-        serde = "1.0"
-
-        [dev-dependencies]
-        serde_derive = "1.0"
-        serde_json = "1.0"
-    };
-
-    let expected = table! {
-        "package" => table! {
-            "name" => "toml".to_owned(),
-            "version" => "0.4.5".to_owned(),
-            "authors" => array! {
-                "Alex Crichton <alex@alexcrichton.com>".to_owned(),
-            },
-        },
-        "badges" => table! {
-            "travis-ci" => table! {
-                "repository" => "alexcrichton/toml-rs".to_owned(),
-            },
-        },
-        "dependencies" => table! {
-            "serde" => "1.0".to_owned(),
-        },
-        "dev-dependencies" => table! {
-            "serde_derive" => "1.0".to_owned(),
-            "serde_json" => "1.0".to_owned(),
-        },
-    };
-
-    assert_eq!(actual, expected);
-}
-
-#[test]
-fn test_array() {
-    // Copied from the TOML spec.
-    let actual = toml_edit::easy::toml! {
-        [[fruit]]
-        name = "apple"
-
-        [fruit.physical]
-        color = "red"
-        shape = "round"
-
-        [[fruit.variety]]
-        name = "red delicious"
-
-        [[fruit.variety]]
-        name = "granny smith"
-
-        [[fruit]]
-        name = "banana"
-
-        [[fruit.variety]]
-        name = "plantain"
-    };
-
-    let expected = table! {
-        "fruit" => array! {
-            table! {
-                "name" => "apple",
-                "physical" => table! {
-                    "color" => "red",
-                    "shape" => "round",
-                },
-                "variety" => array! {
-                    table! {
-                        "name" => "red delicious",
-                    },
-                    table! {
-                        "name" => "granny smith",
-                    },
-                },
-            },
-            table! {
-                "name" => "banana",
-                "variety" => array! {
-                    table! {
-                        "name" => "plantain",
-                    },
-                },
-            },
-        },
-    };
-
-    assert_eq!(actual, expected);
-}
-
-#[test]
-fn test_number() {
-    #![allow(clippy::unusual_byte_groupings)]
-    #![allow(clippy::approx_constant)]
-
-    let actual = toml_edit::easy::toml! {
-        positive = 1
-        negative = -1
-        table = { positive = 1, negative = -1 }
-        array = [ 1, -1 ]
-        neg_zero = -0
-        pos_zero = +0
-        float = 3.14
-
-        sf1 = inf
-        sf2 = +inf
-        sf3 = -inf
-        sf7 = +0.0
-        sf8 = -0.0
-
-        hex = 0xa_b_c
-        oct = 0o755
-        bin = 0b11010110
-    };
-
-    let expected = table! {
-        "positive" => 1,
-        "negative" => -1,
-        "table" => table! {
-            "positive" => 1,
-            "negative" => -1,
-        },
-        "array" => array! {
-            1,
-            -1,
-        },
-        "neg_zero" => -0,
-        "pos_zero" => 0,
-        "float" => 3.14,
-        "sf1" => f64::INFINITY,
-        "sf2" => f64::INFINITY,
-        "sf3" => f64::NEG_INFINITY,
-        "sf7" => 0.0,
-        "sf8" => -0.0,
-        "hex" => 2748,
-        "oct" => 493,
-        "bin" => 214,
-    };
-
-    assert_eq!(actual, expected);
-}
-
-#[test]
-fn test_nan() {
-    let actual = toml_edit::easy::toml! {
-        sf4 = nan
-        sf5 = +nan
-        sf6 = -nan
-    };
-    assert!(actual["sf4"].as_float().unwrap().is_nan());
-    assert!(actual["sf5"].as_float().unwrap().is_nan());
-    assert!(actual["sf6"].as_float().unwrap().is_nan());
-}
-
-#[test]
-fn test_datetime() {
-    let actual = toml_edit::easy::toml! {
-        // Copied from the TOML spec.
-        odt1 = 1979-05-27T07:32:00Z
-        odt2 = 1979-05-27T00:32:00-07:00
-        odt3 = 1979-05-27T00:32:00.999999-07:00
-        odt4 = 1979-05-27 07:32:00Z
-        ldt1 = 1979-05-27T07:32:00
-        ldt2 = 1979-05-27T00:32:00.999999
-        ld1 = 1979-05-27
-        lt1 = 07:32:00
-        lt2 = 00:32:00.999999
-
-        table = {
-            odt1 = 1979-05-27T07:32:00Z,
-            odt2 = 1979-05-27T00:32:00-07:00,
-            odt3 = 1979-05-27T00:32:00.999999-07:00,
-            odt4 = 1979-05-27 07:32:00Z,
-            ldt1 = 1979-05-27T07:32:00,
-            ldt2 = 1979-05-27T00:32:00.999999,
-            ld1 = 1979-05-27,
-            lt1 = 07:32:00,
-            lt2 = 00:32:00.999999,
-        }
-
-        array = [
-            1979-05-27T07:32:00Z,
-            1979-05-27T00:32:00-07:00,
-            1979-05-27T00:32:00.999999-07:00,
-            1979-05-27 07:32:00Z,
-            1979-05-27T07:32:00,
-            1979-05-27T00:32:00.999999,
-            1979-05-27,
-            07:32:00,
-            00:32:00.999999,
-        ]
-    };
-
-    let expected = table! {
-        "odt1" => datetime!("1979-05-27T07:32:00Z"),
-        "odt2" => datetime!("1979-05-27T00:32:00-07:00"),
-        "odt3" => datetime!("1979-05-27T00:32:00.999999-07:00"),
-        "odt4" => datetime!("1979-05-27 07:32:00Z"),
-        "ldt1" => datetime!("1979-05-27T07:32:00"),
-        "ldt2" => datetime!("1979-05-27T00:32:00.999999"),
-        "ld1" => datetime!("1979-05-27"),
-        "lt1" => datetime!("07:32:00"),
-        "lt2" => datetime!("00:32:00.999999"),
-
-        "table" => table! {
-            "odt1" => datetime!("1979-05-27T07:32:00Z"),
-            "odt2" => datetime!("1979-05-27T00:32:00-07:00"),
-            "odt3" => datetime!("1979-05-27T00:32:00.999999-07:00"),
-            "odt4" => datetime!("1979-05-27 07:32:00Z"),
-            "ldt1" => datetime!("1979-05-27T07:32:00"),
-            "ldt2" => datetime!("1979-05-27T00:32:00.999999"),
-            "ld1" => datetime!("1979-05-27"),
-            "lt1" => datetime!("07:32:00"),
-            "lt2" => datetime!("00:32:00.999999"),
-        },
-
-        "array" => array! {
-            datetime!("1979-05-27T07:32:00Z"),
-            datetime!("1979-05-27T00:32:00-07:00"),
-            datetime!("1979-05-27T00:32:00.999999-07:00"),
-            datetime!("1979-05-27 07:32:00Z"),
-            datetime!("1979-05-27T07:32:00"),
-            datetime!("1979-05-27T00:32:00.999999"),
-            datetime!("1979-05-27"),
-            datetime!("07:32:00"),
-            datetime!("00:32:00.999999"),
-        },
-    };
-
-    assert_eq!(actual, expected);
-}
-
-// This test requires rustc >= 1.20.
-#[test]
-fn test_quoted_key() {
-    let actual = toml_edit::easy::toml! {
-        "quoted" = true
-        table = { "quoted" = true }
-
-        [target."cfg(windows)".dependencies]
-        winapi = "0.2.8"
-    };
-
-    let expected = table! {
-        "quoted" => true,
-        "table" => table! {
-            "quoted" => true,
-        },
-        "target" => table! {
-            "cfg(windows)" => table! {
-                "dependencies" => table! {
-                    "winapi" => "0.2.8",
-                },
-            },
-        },
-    };
-
-    assert_eq!(actual, expected);
-}
-
-#[test]
-fn test_empty() {
-    let actual = toml_edit::easy::toml! {
-        empty_inline_table = {}
-        empty_inline_array = []
-
-        [empty_table]
-
-        [[empty_array]]
-    };
-
-    let expected = table! {
-        "empty_inline_table" => table! {},
-        "empty_inline_array" => array! {},
-        "empty_table" => table! {},
-        "empty_array" => array! {
-            table! {},
-        },
-    };
-
-    assert_eq!(actual, expected);
-}
-
-#[test]
-fn test_dotted_keys() {
-    let actual = toml_edit::easy::toml! {
-        a.b = 123
-        a.c = 1979-05-27T07:32:00Z
-        [table]
-        a.b.c = 1
-        a  .  b  .  d = 2
-        in = { type.name = "cat", type.color = "blue" }
-    };
-
-    let expected = table! {
-        "a" => table! {
-            "b" => 123,
-            "c" => datetime!("1979-05-27T07:32:00Z"),
-        },
-        "table" => table! {
-            "a" => table! {
-                "b" => table! {
-                    "c" => 1,
-                    "d" => 2,
-                },
-            },
-            "in" => table! {
-                "type" => table! {
-                    "name" => "cat",
-                    "color" => "blue",
-                },
-            },
-        },
-    };
-
-    assert_eq!(actual, expected);
-}
diff --git a/crates/toml_edit/tests/testsuite/main.rs b/crates/toml_edit/tests/testsuite/main.rs
index 76f3497e..1476c5da 100644
--- a/crates/toml_edit/tests/testsuite/main.rs
+++ b/crates/toml_edit/tests/testsuite/main.rs
@@ -1,14 +1,8 @@
 #![recursion_limit = "256"]
 
 mod convert;
+mod datetime;
 mod edit;
-mod enum_external_deserialize;
-mod formatter;
 mod invalid;
-mod macros;
 mod parse;
-mod pretty;
-mod serde;
-mod spanned;
-mod spanned_impls;
 mod stackoverflow;
diff --git a/crates/toml_edit/tests/testsuite/parse.rs b/crates/toml_edit/tests/testsuite/parse.rs
index 7a89126d..ed3ca303 100644
--- a/crates/toml_edit/tests/testsuite/parse.rs
+++ b/crates/toml_edit/tests/testsuite/parse.rs
@@ -93,3 +93,1356 @@ fn test_key_unification() {
 
     assert_eq(expected, doc.to_string());
 }
+
+macro_rules! bad {
+    ($toml:expr, $msg:expr) => {
+        match $toml.parse::<Document>() {
+            Ok(s) => panic!("parsed to: {:#?}", s),
+            Err(e) => snapbox::assert_eq($msg, e.to_string()),
+        }
+    };
+}
+
+#[test]
+fn crlf() {
+    "\
+     [project]\r\n\
+     \r\n\
+     name = \"splay\"\r\n\
+     version = \"0.1.0\"\r\n\
+     authors = [\"alex@crichton.co\"]\r\n\
+     \r\n\
+     [[lib]]\r\n\
+     \r\n\
+     path = \"lib.rs\"\r\n\
+     name = \"splay\"\r\n\
+     description = \"\"\"\
+     A Rust implementation of a TAR file reader and writer. This library does not\r\n\
+     currently handle compression, but it is abstract over all I/O readers and\r\n\
+     writers. Additionally, great lengths are taken to ensure that the entire\r\n\
+     contents are never required to be entirely resident in memory all at once.\r\n\
+     \"\"\"\
+     "
+    .parse::<Document>()
+    .unwrap();
+}
+
+#[test]
+fn fun_with_strings() {
+    let table = r#"
+bar = "\U00000000"
+key1 = "One\nTwo"
+key2 = """One\nTwo"""
+key3 = """
+One
+Two"""
+
+key4 = "The quick brown fox jumps over the lazy dog."
+key5 = """
+The quick brown \
+
+
+fox jumps over \
+the lazy dog."""
+key6 = """\
+   The quick brown \
+   fox jumps over \
+   the lazy dog.\
+   """
+# What you see is what you get.
+winpath  = 'C:\Users\nodejs\templates'
+winpath2 = '\\ServerX\admin$\system32\'
+quoted   = 'Tom "Dubs" Preston-Werner'
+regex    = '<\i\c*\s*>'
+
+regex2 = '''I [dw]on't need \d{2} apples'''
+lines  = '''
+The first newline is
+trimmed in raw strings.
+All other whitespace
+is preserved.
+'''
+"#
+    .parse::<Document>()
+    .unwrap();
+    assert_eq!(table["bar"].as_str(), Some("\0"));
+    assert_eq!(table["key1"].as_str(), Some("One\nTwo"));
+    assert_eq!(table["key2"].as_str(), Some("One\nTwo"));
+    assert_eq!(table["key3"].as_str(), Some("One\nTwo"));
+
+    let msg = "The quick brown fox jumps over the lazy dog.";
+    assert_eq!(table["key4"].as_str(), Some(msg));
+    assert_eq!(table["key5"].as_str(), Some(msg));
+    assert_eq!(table["key6"].as_str(), Some(msg));
+
+    assert_eq!(
+        table["winpath"].as_str(),
+        Some(r"C:\Users\nodejs\templates")
+    );
+    assert_eq!(
+        table["winpath2"].as_str(),
+        Some(r"\\ServerX\admin$\system32\")
+    );
+    assert_eq!(
+        table["quoted"].as_str(),
+        Some(r#"Tom "Dubs" Preston-Werner"#)
+    );
+    assert_eq!(table["regex"].as_str(), Some(r"<\i\c*\s*>"));
+    assert_eq!(
+        table["regex2"].as_str(),
+        Some(r"I [dw]on't need \d{2} apples")
+    );
+    assert_eq!(
+        table["lines"].as_str(),
+        Some(
+            "The first newline is\n\
+             trimmed in raw strings.\n\
+             All other whitespace\n\
+             is preserved.\n"
+        )
+    );
+}
+
+#[test]
+fn tables_in_arrays() {
+    let table = r#"
+[[foo]]
+#…
+[foo.bar]
+#…
+
+[[foo]] # ...
+#…
+[foo.bar]
+#...
+"#
+    .parse::<Document>()
+    .unwrap();
+    table["foo"][0]["bar"].as_table().unwrap();
+    table["foo"][1]["bar"].as_table().unwrap();
+}
+
+#[test]
+fn empty_table() {
+    let table = r#"
+[foo]"#
+        .parse::<Document>()
+        .unwrap();
+    table["foo"].as_table().unwrap();
+}
+
+#[test]
+fn fruit() {
+    let table = r#"
+[[fruit]]
+name = "apple"
+
+[fruit.physical]
+color = "red"
+shape = "round"
+
+[[fruit.variety]]
+name = "red delicious"
+
+[[fruit.variety]]
+name = "granny smith"
+
+[[fruit]]
+name = "banana"
+
+[[fruit.variety]]
+name = "plantain"
+"#
+    .parse::<Document>()
+    .unwrap();
+    assert_eq!(table["fruit"][0]["name"].as_str(), Some("apple"));
+    assert_eq!(table["fruit"][0]["physical"]["color"].as_str(), Some("red"));
+    assert_eq!(
+        table["fruit"][0]["physical"]["shape"].as_str(),
+        Some("round")
+    );
+    assert_eq!(
+        table["fruit"][0]["variety"][0]["name"].as_str(),
+        Some("red delicious")
+    );
+    assert_eq!(
+        table["fruit"][0]["variety"][1]["name"].as_str(),
+        Some("granny smith")
+    );
+    assert_eq!(table["fruit"][1]["name"].as_str(), Some("banana"));
+    assert_eq!(
+        table["fruit"][1]["variety"][0]["name"].as_str(),
+        Some("plantain")
+    );
+}
+
+#[test]
+fn stray_cr() {
+    bad!(
+        "\r",
+        "\
+TOML parse error at line 1, column 1
+  |
+1 | \r
+  | ^
+
+"
+    );
+    bad!(
+        "a = [ \r ]",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | a = [ \r
+ ]
+  |       ^
+invalid array
+expected `]`
+"
+    );
+    bad!(
+        "a = \"\"\"\r\"\"\"",
+        "\
+TOML parse error at line 1, column 8
+  |
+1 | a = \"\"\"\r
+\"\"\"
+  |        ^
+invalid multiline basic string
+"
+    );
+    bad!(
+        "a = \"\"\"\\  \r  \"\"\"",
+        "\
+TOML parse error at line 1, column 10
+  |
+1 | a = \"\"\"\\  \r
+  \"\"\"
+  |          ^
+invalid escape sequence
+expected `b`, `f`, `n`, `r`, `t`, `u`, `U`, `\\`, `\"`
+"
+    );
+    bad!(
+        "a = '''\r'''",
+        "\
+TOML parse error at line 1, column 8
+  |
+1 | a = '''\r
+'''
+  |        ^
+invalid multiline literal string
+"
+    );
+    bad!(
+        "a = '\r'",
+        "\
+TOML parse error at line 1, column 6
+  |
+1 | a = '\r
+'
+  |      ^
+invalid literal string
+"
+    );
+    bad!(
+        "a = \"\r\"",
+        "\
+TOML parse error at line 1, column 6
+  |
+1 | a = \"\r
+\"
+  |      ^
+invalid basic string
+"
+    );
+}
+
+#[test]
+fn blank_literal_string() {
+    let table = "foo = ''".parse::<Document>().unwrap();
+    assert_eq!(table["foo"].as_str(), Some(""));
+}
+
+#[test]
+fn many_blank() {
+    let table = "foo = \"\"\"\n\n\n\"\"\"".parse::<Document>().unwrap();
+    assert_eq!(table["foo"].as_str(), Some("\n\n"));
+}
+
+#[test]
+fn literal_eats_crlf() {
+    let table = "
+        foo = \"\"\"\\\r\n\"\"\"
+        bar = \"\"\"\\\r\n   \r\n   \r\n   a\"\"\"
+    "
+    .parse::<Document>()
+    .unwrap();
+    assert_eq!(table["foo"].as_str(), Some(""));
+    assert_eq!(table["bar"].as_str(), Some("a"));
+}
+
+#[test]
+fn string_no_newline() {
+    bad!(
+        "a = \"\n\"",
+        "\
+TOML parse error at line 1, column 6
+  |
+1 | a = \"
+  |      ^
+invalid basic string
+"
+    );
+    bad!(
+        "a = '\n'",
+        "\
+TOML parse error at line 1, column 6
+  |
+1 | a = '
+  |      ^
+invalid literal string
+"
+    );
+}
+
+#[test]
+fn bad_leading_zeros() {
+    bad!(
+        "a = 00",
+        "\
+TOML parse error at line 1, column 6
+  |
+1 | a = 00
+  |      ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "a = -00",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | a = -00
+  |       ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "a = +00",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | a = +00
+  |       ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "a = 00.0",
+        "\
+TOML parse error at line 1, column 6
+  |
+1 | a = 00.0
+  |      ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "a = -00.0",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | a = -00.0
+  |       ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "a = +00.0",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | a = +00.0
+  |       ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "a = 9223372036854775808",
+        "\
+TOML parse error at line 1, column 5
+  |
+1 | a = 9223372036854775808
+  |     ^
+number too large to fit in target type
+"
+    );
+    bad!(
+        "a = -9223372036854775809",
+        "\
+TOML parse error at line 1, column 5
+  |
+1 | a = -9223372036854775809
+  |     ^
+number too small to fit in target type
+"
+    );
+}
+
+#[test]
+fn bad_floats() {
+    bad!(
+        "a = 0.",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | a = 0.
+  |       ^
+invalid floating-point number
+expected digit
+"
+    );
+    bad!(
+        "a = 0.e",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | a = 0.e
+  |       ^
+invalid floating-point number
+expected digit
+"
+    );
+    bad!(
+        "a = 0.E",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | a = 0.E
+  |       ^
+invalid floating-point number
+expected digit
+"
+    );
+    bad!(
+        "a = 0.0E",
+        "\
+TOML parse error at line 1, column 9
+  |
+1 | a = 0.0E
+  |         ^
+invalid floating-point number
+"
+    );
+    bad!(
+        "a = 0.0e",
+        "\
+TOML parse error at line 1, column 9
+  |
+1 | a = 0.0e
+  |         ^
+invalid floating-point number
+"
+    );
+    bad!(
+        "a = 0.0e-",
+        "\
+TOML parse error at line 1, column 10
+  |
+1 | a = 0.0e-
+  |          ^
+invalid floating-point number
+"
+    );
+    bad!(
+        "a = 0.0e+",
+        "\
+TOML parse error at line 1, column 10
+  |
+1 | a = 0.0e+
+  |          ^
+invalid floating-point number
+"
+    );
+}
+
+#[test]
+fn floats() {
+    macro_rules! t {
+        ($actual:expr, $expected:expr) => {{
+            let f = format!("foo = {}", $actual);
+            println!("{}", f);
+            let a = f.parse::<Document>().unwrap();
+            assert_eq!(a["foo"].as_float().unwrap(), $expected);
+        }};
+    }
+
+    t!("1.0", 1.0);
+    t!("1.0e0", 1.0);
+    t!("1.0e+0", 1.0);
+    t!("1.0e-0", 1.0);
+    t!("1E-0", 1.0);
+    t!("1.001e-0", 1.001);
+    t!("2e10", 2e10);
+    t!("2e+10", 2e10);
+    t!("2e-10", 2e-10);
+    t!("2_0.0", 20.0);
+    t!("2_0.0_0e1_0", 20.0e10);
+    t!("2_0.1_0e1_0", 20.1e10);
+}
+
+#[test]
+fn bare_key_names() {
+    let a = "
+        foo = 3
+        foo_3 = 3
+        foo_-2--3--r23f--4-f2-4 = 3
+        _ = 3
+        - = 3
+        8 = 8
+        \"a\" = 3
+        \"!\" = 3
+        \"a^b\" = 3
+        \"\\\"\" = 3
+        \"character encoding\" = \"value\"
+        'ʎǝʞ' = \"value\"
+    "
+    .parse::<Document>()
+    .unwrap();
+    let _ = &a["foo"];
+    let _ = &a["-"];
+    let _ = &a["_"];
+    let _ = &a["8"];
+    let _ = &a["foo_3"];
+    let _ = &a["foo_-2--3--r23f--4-f2-4"];
+    let _ = &a["a"];
+    let _ = &a["!"];
+    let _ = &a["\""];
+    let _ = &a["character encoding"];
+    let _ = &a["ʎǝʞ"];
+}
+
+#[test]
+fn bad_keys() {
+    bad!(
+        "key\n=3",
+        "\
+TOML parse error at line 1, column 4
+  |
+1 | key
+  |    ^
+expected `.`, `=`
+"
+    );
+    bad!(
+        "key=\n3",
+        "\
+TOML parse error at line 1, column 5
+  |
+1 | key=
+  |     ^
+invalid string
+expected `\"`, `'`
+"
+    );
+    bad!(
+        "key|=3",
+        "\
+TOML parse error at line 1, column 4
+  |
+1 | key|=3
+  |    ^
+expected `.`, `=`
+"
+    );
+    bad!(
+        "=3",
+        "\
+TOML parse error at line 1, column 1
+  |
+1 | =3
+  | ^
+invalid key
+"
+    );
+    bad!(
+        "\"\"|=3",
+        "\
+TOML parse error at line 1, column 3
+  |
+1 | \"\"|=3
+  |   ^
+expected `.`, `=`
+"
+    );
+    bad!(
+        "\"\n\"|=3",
+        "\
+TOML parse error at line 1, column 2
+  |
+1 | \"
+  |  ^
+invalid basic string
+"
+    );
+    bad!(
+        "\"\r\"|=3",
+        "\
+TOML parse error at line 1, column 2
+  |
+1 | \"\r\"|=3
+  |  ^
+invalid basic string
+"
+    );
+    bad!(
+        "''''''=3",
+        "\
+TOML parse error at line 1, column 3
+  |
+1 | ''''''=3
+  |   ^
+expected `.`, `=`
+"
+    );
+    bad!(
+        "\"\"\"\"\"\"=3",
+        "\
+TOML parse error at line 1, column 3
+  |
+1 | \"\"\"\"\"\"=3
+  |   ^
+expected `.`, `=`
+"
+    );
+    bad!(
+        "'''key'''=3",
+        "\
+TOML parse error at line 1, column 3
+  |
+1 | '''key'''=3
+  |   ^
+expected `.`, `=`
+"
+    );
+    bad!(
+        "\"\"\"key\"\"\"=3",
+        "\
+TOML parse error at line 1, column 3
+  |
+1 | \"\"\"key\"\"\"=3
+  |   ^
+expected `.`, `=`
+"
+    );
+}
+
+#[test]
+fn bad_table_names() {
+    bad!(
+        "[]",
+        "\
+TOML parse error at line 1, column 2
+  |
+1 | []
+  |  ^
+invalid key
+"
+    );
+    bad!(
+        "[.]",
+        "\
+TOML parse error at line 1, column 2
+  |
+1 | [.]
+  |  ^
+invalid key
+"
+    );
+    bad!(
+        "[a.]",
+        "\
+TOML parse error at line 1, column 3
+  |
+1 | [a.]
+  |   ^
+invalid table header
+expected `.`, `]`
+"
+    );
+    bad!(
+        "[!]",
+        "\
+TOML parse error at line 1, column 2
+  |
+1 | [!]
+  |  ^
+invalid key
+"
+    );
+    bad!(
+        "[\"\n\"]",
+        "\
+TOML parse error at line 1, column 3
+  |
+1 | [\"
+  |   ^
+invalid basic string
+"
+    );
+    bad!(
+        "[a.b]\n[a.\"b\"]",
+        "\
+TOML parse error at line 2, column 1
+  |
+2 | [a.\"b\"]
+  | ^
+invalid table header
+duplicate key `b` in table `a`
+"
+    );
+    bad!(
+        "[']",
+        "\
+TOML parse error at line 1, column 4
+  |
+1 | [']
+  |    ^
+invalid literal string
+"
+    );
+    bad!(
+        "[''']",
+        "\
+TOML parse error at line 1, column 4
+  |
+1 | [''']
+  |    ^
+invalid table header
+expected `.`, `]`
+"
+    );
+    bad!(
+        "['''''']",
+        "\
+TOML parse error at line 1, column 4
+  |
+1 | ['''''']
+  |    ^
+invalid table header
+expected `.`, `]`
+"
+    );
+    bad!(
+        "['''foo''']",
+        "\
+TOML parse error at line 1, column 4
+  |
+1 | ['''foo''']
+  |    ^
+invalid table header
+expected `.`, `]`
+"
+    );
+    bad!(
+        "[\"\"\"bar\"\"\"]",
+        "\
+TOML parse error at line 1, column 4
+  |
+1 | [\"\"\"bar\"\"\"]
+  |    ^
+invalid table header
+expected `.`, `]`
+"
+    );
+    bad!(
+        "['\n']",
+        "\
+TOML parse error at line 1, column 3
+  |
+1 | ['
+  |   ^
+invalid literal string
+"
+    );
+    bad!(
+        "['\r\n']",
+        "\
+TOML parse error at line 1, column 3
+  |
+1 | ['
+  |   ^
+invalid literal string
+"
+    );
+}
+
+#[test]
+fn table_names() {
+    let a = "
+        [a.\"b\"]
+        [\"f f\"]
+        [\"f.f\"]
+        [\"\\\"\"]
+        ['a.a']
+        ['\"\"']
+    "
+    .parse::<Document>()
+    .unwrap();
+    println!("{:?}", a);
+    let _ = &a["a"]["b"];
+    let _ = &a["f f"];
+    let _ = &a["f.f"];
+    let _ = &a["\""];
+    let _ = &a["\"\""];
+}
+
+#[test]
+fn invalid_bare_numeral() {
+    bad!(
+        "4",
+        "\
+TOML parse error at line 1, column 2
+  |
+1 | 4
+  |  ^
+expected `.`, `=`
+"
+    );
+}
+
+#[test]
+fn inline_tables() {
+    "a = {}".parse::<Document>().unwrap();
+    "a = {b=1}".parse::<Document>().unwrap();
+    "a = {   b   =   1    }".parse::<Document>().unwrap();
+    "a = {a=1,b=2}".parse::<Document>().unwrap();
+    "a = {a=1,b=2,c={}}".parse::<Document>().unwrap();
+
+    bad!(
+        "a = {a=1,}",
+        "\
+TOML parse error at line 1, column 9
+  |
+1 | a = {a=1,}
+  |         ^
+invalid inline table
+expected `}`
+"
+    );
+    bad!(
+        "a = {,}",
+        "\
+TOML parse error at line 1, column 6
+  |
+1 | a = {,}
+  |      ^
+invalid inline table
+expected `}`
+"
+    );
+    bad!(
+        "a = {a=1,a=1}",
+        "\
+TOML parse error at line 1, column 6
+  |
+1 | a = {a=1,a=1}
+  |      ^
+duplicate key `a`
+"
+    );
+    bad!(
+        "a = {\n}",
+        "\
+TOML parse error at line 1, column 6
+  |
+1 | a = {
+  |      ^
+invalid inline table
+expected `}`
+"
+    );
+    bad!(
+        "a = {",
+        "\
+TOML parse error at line 1, column 6
+  |
+1 | a = {
+  |      ^
+invalid inline table
+expected `}`
+"
+    );
+
+    "a = {a=[\n]}".parse::<Document>().unwrap();
+    "a = {\"a\"=[\n]}".parse::<Document>().unwrap();
+    "a = [\n{},\n{},\n]".parse::<Document>().unwrap();
+}
+
+#[test]
+fn number_underscores() {
+    macro_rules! t {
+        ($actual:expr, $expected:expr) => {{
+            let f = format!("foo = {}", $actual);
+            let table = f.parse::<Document>().unwrap();
+            assert_eq!(table["foo"].as_integer().unwrap(), $expected);
+        }};
+    }
+
+    t!("1_0", 10);
+    t!("1_0_0", 100);
+    t!("1_000", 1000);
+    t!("+1_000", 1000);
+    t!("-1_000", -1000);
+}
+
+#[test]
+fn bad_underscores() {
+    bad!(
+        "foo = 0_",
+        "\
+TOML parse error at line 1, column 8
+  |
+1 | foo = 0_
+  |        ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "foo = 0__0",
+        "\
+TOML parse error at line 1, column 8
+  |
+1 | foo = 0__0
+  |        ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "foo = __0",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | foo = __0
+  |       ^
+invalid integer
+expected leading digit
+"
+    );
+    bad!(
+        "foo = 1_0_",
+        "\
+TOML parse error at line 1, column 11
+  |
+1 | foo = 1_0_
+  |           ^
+invalid integer
+expected digit
+"
+    );
+}
+
+#[test]
+fn bad_unicode_codepoint() {
+    bad!(
+        "foo = \"\\uD800\"",
+        "\
+TOML parse error at line 1, column 10
+  |
+1 | foo = \"\\uD800\"
+  |          ^
+invalid unicode 4-digit hex code
+value is out of range
+"
+    );
+}
+
+#[test]
+fn bad_strings() {
+    bad!(
+        "foo = \"\\uxx\"",
+        "\
+TOML parse error at line 1, column 10
+  |
+1 | foo = \"\\uxx\"
+  |          ^
+invalid unicode 4-digit hex code
+"
+    );
+    bad!(
+        "foo = \"\\u\"",
+        "\
+TOML parse error at line 1, column 10
+  |
+1 | foo = \"\\u\"
+  |          ^
+invalid unicode 4-digit hex code
+"
+    );
+    bad!(
+        "foo = \"\\",
+        "\
+TOML parse error at line 1, column 8
+  |
+1 | foo = \"\\
+  |        ^
+invalid basic string
+"
+    );
+    bad!(
+        "foo = '",
+        "\
+TOML parse error at line 1, column 8
+  |
+1 | foo = '
+  |        ^
+invalid literal string
+"
+    );
+}
+
+#[test]
+fn empty_string() {
+    assert_eq!(
+        "foo = \"\"".parse::<Document>().unwrap()["foo"]
+            .as_str()
+            .unwrap(),
+        ""
+    );
+}
+
+#[test]
+fn booleans() {
+    let table = "foo = true".parse::<Document>().unwrap();
+    assert_eq!(table["foo"].as_bool(), Some(true));
+
+    let table = "foo = false".parse::<Document>().unwrap();
+    assert_eq!(table["foo"].as_bool(), Some(false));
+
+    bad!(
+        "foo = true2",
+        "\
+TOML parse error at line 1, column 11
+  |
+1 | foo = true2
+  |           ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "foo = false2",
+        "\
+TOML parse error at line 1, column 12
+  |
+1 | foo = false2
+  |            ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "foo = t1",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | foo = t1
+  |       ^
+invalid string
+expected `\"`, `'`
+"
+    );
+    bad!(
+        "foo = f2",
+        "\
+TOML parse error at line 1, column 7
+  |
+1 | foo = f2
+  |       ^
+invalid string
+expected `\"`, `'`
+"
+    );
+}
+
+#[test]
+fn bad_nesting() {
+    bad!(
+        "
+        a = [2]
+        [[a]]
+        b = 5
+        ",
+        "\
+TOML parse error at line 3, column 9
+  |
+3 |         [[a]]
+  |         ^
+invalid table header
+duplicate key `a` in document root
+"
+    );
+    bad!(
+        "
+        a = 1
+        [a.b]
+        ",
+        "\
+TOML parse error at line 3, column 9
+  |
+3 |         [a.b]
+  |         ^
+invalid table header
+dotted key `a` attempted to extend non-table type (integer)
+"
+    );
+    bad!(
+        "
+        a = []
+        [a.b]
+        ",
+        "\
+TOML parse error at line 3, column 9
+  |
+3 |         [a.b]
+  |         ^
+invalid table header
+dotted key `a` attempted to extend non-table type (array)
+"
+    );
+    bad!(
+        "
+        a = []
+        [[a.b]]
+        ",
+        "\
+TOML parse error at line 3, column 9
+  |
+3 |         [[a.b]]
+  |         ^
+invalid table header
+dotted key `a` attempted to extend non-table type (array)
+"
+    );
+    bad!(
+        "
+        [a]
+        b = { c = 2, d = {} }
+        [a.b]
+        c = 2
+        ",
+        "\
+TOML parse error at line 4, column 9
+  |
+4 |         [a.b]
+  |         ^
+invalid table header
+duplicate key `b` in table `a`
+"
+    );
+}
+
+#[test]
+fn bad_table_redefine() {
+    bad!(
+        "
+        [a]
+        foo=\"bar\"
+        [a.b]
+        foo=\"bar\"
+        [a]
+        ",
+        "\
+TOML parse error at line 6, column 9
+  |
+6 |         [a]
+  |         ^
+invalid table header
+duplicate key `a` in document root
+"
+    );
+    bad!(
+        "
+        [a]
+        foo=\"bar\"
+        b = { foo = \"bar\" }
+        [a]
+        ",
+        "\
+TOML parse error at line 5, column 9
+  |
+5 |         [a]
+  |         ^
+invalid table header
+duplicate key `a` in document root
+"
+    );
+    bad!(
+        "
+        [a]
+        b = {}
+        [a.b]
+        ",
+        "\
+TOML parse error at line 4, column 9
+  |
+4 |         [a.b]
+  |         ^
+invalid table header
+duplicate key `b` in table `a`
+"
+    );
+
+    bad!(
+        "
+        [a]
+        b = {}
+        [a]
+        ",
+        "\
+TOML parse error at line 4, column 9
+  |
+4 |         [a]
+  |         ^
+invalid table header
+duplicate key `a` in document root
+"
+    );
+}
+
+#[test]
+fn datetimes() {
+    macro_rules! t {
+        ($actual:expr) => {{
+            let f = format!("foo = {}", $actual);
+            let toml = f.parse::<Document>().expect(&format!("failed: {}", f));
+            assert_eq!(toml["foo"].as_datetime().unwrap().to_string(), $actual);
+        }};
+    }
+
+    t!("2016-09-09T09:09:09Z");
+    t!("2016-09-09T09:09:09.1Z");
+    t!("2016-09-09T09:09:09.2+10:00");
+    t!("2016-09-09T09:09:09.123456789-02:00");
+    bad!(
+        "foo = 2016-09-09T09:09:09.Z",
+        "\
+TOML parse error at line 1, column 26
+  |
+1 | foo = 2016-09-09T09:09:09.Z
+  |                          ^
+expected newline, `#`
+"
+    );
+    bad!(
+        "foo = 2016-9-09T09:09:09Z",
+        "\
+TOML parse error at line 1, column 12
+  |
+1 | foo = 2016-9-09T09:09:09Z
+  |            ^
+invalid date-time
+"
+    );
+    bad!(
+        "foo = 2016-09-09T09:09:09+2:00",
+        "\
+TOML parse error at line 1, column 27
+  |
+1 | foo = 2016-09-09T09:09:09+2:00
+  |                           ^
+invalid time offset
+"
+    );
+    bad!(
+        "foo = 2016-09-09T09:09:09-2:00",
+        "\
+TOML parse error at line 1, column 27
+  |
+1 | foo = 2016-09-09T09:09:09-2:00
+  |                           ^
+invalid time offset
+"
+    );
+    bad!(
+        "foo = 2016-09-09T09:09:09Z-2:00",
+        "\
+TOML parse error at line 1, column 27
+  |
+1 | foo = 2016-09-09T09:09:09Z-2:00
+  |                           ^
+expected newline, `#`
+"
+    );
+}
+
+#[test]
+fn require_newline_after_value() {
+    bad!(
+        "0=0r=false",
+        "\
+TOML parse error at line 1, column 4
+  |
+1 | 0=0r=false
+  |    ^
+expected newline, `#`
+"
+    );
+    bad!(
+        r#"
+0=""o=""m=""r=""00="0"q="""0"""e="""0"""
+"#,
+        r#"TOML parse error at line 2, column 5
+  |
+2 | 0=""o=""m=""r=""00="0"q="""0"""e="""0"""
+  |     ^
+expected newline, `#`
+"#
+    );
+    bad!(
+        r#"
+[[0000l0]]
+0="0"[[0000l0]]
+0="0"[[0000l0]]
+0="0"l="0"
+"#,
+        r#"TOML parse error at line 3, column 6
+  |
+3 | 0="0"[[0000l0]]
+  |      ^
+expected newline, `#`
+"#
+    );
+    bad!(
+        r#"
+0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
+"#,
+        r#"TOML parse error at line 2, column 6
+  |
+2 | 0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
+  |      ^
+expected newline, `#`
+"#
+    );
+    bad!(
+        r#"
+0=0r0=0r=false
+"#,
+        r#"TOML parse error at line 2, column 4
+  |
+2 | 0=0r0=0r=false
+  |    ^
+expected newline, `#`
+"#
+    );
+    bad!(
+        r#"
+0=0r0=0r=falsefal=false
+"#,
+        r#"TOML parse error at line 2, column 4
+  |
+2 | 0=0r0=0r=falsefal=false
+  |    ^
+expected newline, `#`
+"#
+    );
+}
diff --git a/crates/toml_edit/tests/testsuite/pretty.rs b/crates/toml_edit/tests/testsuite/pretty.rs
deleted file mode 100644
index 4f87935d..00000000
--- a/crates/toml_edit/tests/testsuite/pretty.rs
+++ /dev/null
@@ -1,163 +0,0 @@
-#![cfg(feature = "easy")]
-
-use snapbox::assert_eq;
-
-const PRETTY_STD: &str = r#"[example]
-array = [
-    "item 1",
-    "item 2",
-]
-empty = []
-one = ["one"]
-oneline = "this has no newlines."
-text = """
-this is the first line
-this is the second line
-"""
-"#;
-
-#[test]
-fn pretty_std() {
-    let toml = PRETTY_STD;
-    let value: toml_edit::easy::Value = toml_edit::easy::from_str(toml).unwrap();
-    let result = toml_edit::easy::to_string_pretty(&value).unwrap();
-    println!("EXPECTED:\n{}", toml);
-    println!("\nRESULT:\n{}", result);
-    assert_eq(toml, &result);
-}
-
-const PRETTY_TRICKY: &str = r##"[example]
-f = "\f"
-glass = """
-Nothing too unusual, except that I can eat glass in:
-- Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα. 
-- Polish: Mogę jeść szkło, i mi nie szkodzi. 
-- Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती. 
-- Japanese: 私はガラスを食べられます。それは私を傷つけません。 
-"""
-r = "\r"
-r_newline = """
-\r
-"""
-single = "this is a single line but has \"\" cuz it\"s tricky"
-single_tricky = "single line with ''' in it"
-tabs = """
-this is pretty standard
-\texcept for some \ttabs right here
-"""
-text = """
-this is the first line.
-This has a ''' in it and \"\"\" cuz it's tricky yo
-Also ' and \" because why not
-this is the fourth line
-"""
-"##;
-
-#[test]
-fn pretty_tricky() {
-    let toml = PRETTY_TRICKY;
-    let value: toml_edit::easy::Value = toml_edit::easy::from_str(toml).unwrap();
-    let result = toml_edit::easy::to_string_pretty(&value).unwrap();
-    println!("EXPECTED:\n{}", toml);
-    println!("\nRESULT:\n{}", result);
-    assert_eq(toml, &result);
-}
-
-const PRETTY_TABLE_ARRAY: &str = r##"[[array]]
-key = "foo"
-
-[[array]]
-key = "bar"
-
-[abc]
-doc = "this is a table"
-
-[example]
-single = "this is a single line string"
-"##;
-
-#[test]
-fn pretty_table_array() {
-    let toml = PRETTY_TABLE_ARRAY;
-    let value: toml_edit::easy::Value = toml_edit::easy::from_str(toml).unwrap();
-    let result = toml_edit::easy::to_string_pretty(&value).unwrap();
-    println!("EXPECTED:\n{}", toml);
-    println!("\nRESULT:\n{}", result);
-    assert_eq(toml, &result);
-}
-
-const TABLE_ARRAY: &str = r##"[[array]]
-key = "foo"
-
-[[array]]
-key = "bar"
-
-[abc]
-doc = "this is a table"
-
-[example]
-single = "this is a single line string"
-"##;
-
-#[test]
-fn table_array() {
-    let toml = TABLE_ARRAY;
-    let value: toml_edit::easy::Value = toml_edit::easy::from_str(toml).unwrap();
-    let result = toml_edit::easy::to_string_pretty(&value).unwrap();
-    println!("EXPECTED:\n{}", toml);
-    println!("\nRESULT:\n{}", result);
-    assert_eq(toml, &result);
-}
-
-const PRETTY_EMPTY_TABLE: &str = r#"[example]
-"#;
-
-#[test]
-fn pretty_empty_table() {
-    let toml = PRETTY_EMPTY_TABLE;
-    let value: toml_edit::easy::Value = toml_edit::easy::from_str(toml).unwrap();
-    let result = toml_edit::easy::to_string_pretty(&value).unwrap();
-    println!("EXPECTED:\n{}", toml);
-    println!("\nRESULT:\n{}", result);
-    assert_eq(toml, &result);
-}
-
-#[test]
-fn error_includes_key() {
-    #[derive(Debug, serde::Serialize, serde::Deserialize)]
-    struct Package {
-        name: String,
-        version: String,
-        authors: Vec<String>,
-        profile: Profile,
-    }
-
-    #[derive(Debug, serde::Serialize, serde::Deserialize)]
-    struct Profile {
-        dev: Dev,
-    }
-
-    #[derive(Debug, serde::Serialize, serde::Deserialize)]
-    struct Dev {
-        debug: U32OrBool,
-    }
-
-    #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq)]
-    #[serde(untagged, expecting = "expected a boolean or an integer")]
-    pub enum U32OrBool {
-        U32(u32),
-        Bool(bool),
-    }
-
-    let raw = r#"name = "foo"
-version = "0.0.0"
-authors = []
-
-[profile.dev]
-debug = true
-"#;
-
-    let pkg: Package = toml_edit::de::from_str(raw).unwrap();
-    let pretty = toml_edit::ser::to_string_pretty(&pkg).unwrap();
-    assert_eq(raw, pretty);
-}
diff --git a/crates/toml_edit/tests/testsuite/serde.rs b/crates/toml_edit/tests/testsuite/serde.rs
deleted file mode 100644
index 2d0e3f35..00000000
--- a/crates/toml_edit/tests/testsuite/serde.rs
+++ /dev/null
@@ -1,802 +0,0 @@
-#![cfg(feature = "easy")]
-
-use std::collections::BTreeMap;
-
-use serde::{Deserialize, Serialize};
-use snapbox::assert_eq;
-use toml_edit::easy::map::Map;
-use toml_edit::easy::Value;
-use toml_edit::easy::Value::{Array, Float, Integer, Table};
-
-macro_rules! t {
-    ($e:expr) => {
-        match $e {
-            Ok(t) => t,
-            Err(e) => panic!("{} failed with {}", stringify!($e), e),
-        }
-    };
-}
-
-macro_rules! equivalent {
-    ($literal:expr, $toml:expr,) => {{
-        let toml = $toml;
-        let literal = $literal;
-
-        // In/out of Value is equivalent
-        println!("try_from");
-        assert_eq!(t!(Value::try_from(literal.clone())), toml);
-        println!("try_into");
-        assert_eq!(literal, t!(toml.clone().try_into()));
-
-        // Through a string equivalent
-        println!("to_string(literal)");
-        snapbox::assert_eq(
-            toml.to_string(),
-            t!(toml_edit::easy::to_string_pretty(&literal)),
-        );
-        println!("to_string(toml)");
-        snapbox::assert_eq(
-            toml.to_string(),
-            t!(toml_edit::easy::to_string_pretty(&toml)),
-        );
-        println!("literal, from_str(toml)");
-        assert_eq!(literal, t!(toml_edit::easy::from_str(&toml.to_string())));
-        println!("toml, from_str(toml)");
-        assert_eq!(toml, t!(toml_edit::easy::from_str(&toml.to_string())));
-    }};
-}
-
-macro_rules! error {
-    ($ty:ty, $toml:expr, $msg_parse:expr, $msg_decode:expr) => {{
-        println!("attempting parsing");
-        match toml_edit::easy::from_str::<$ty>(&$toml.to_string()) {
-            Ok(_) => panic!("successful"),
-            Err(e) => snapbox::assert_eq($msg_parse, e.to_string()),
-        }
-
-        println!("attempting toml decoding");
-        match $toml.try_into::<$ty>() {
-            Ok(_) => panic!("successful"),
-            Err(e) => snapbox::assert_eq($msg_decode, e.to_string()),
-        }
-    }};
-}
-
-macro_rules! map( ($($k:ident: $v:expr),*) => ({
-    let mut _m = Map::new();
-    $(_m.insert(stringify!($k).to_string(), $v);)*
-    _m
-}) );
-
-#[test]
-fn smoke() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a: isize,
-    }
-
-    equivalent!(Foo { a: 2 }, Table(map! { a: Integer(2) }),);
-}
-
-#[test]
-fn smoke_hyphen() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a_b: isize,
-    }
-
-    equivalent! {
-        Foo { a_b: 2 },
-        Table(map! { a_b: Integer(2) }),
-    }
-
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo2 {
-        #[serde(rename = "a-b")]
-        a_b: isize,
-    }
-
-    let mut m = Map::new();
-    m.insert("a-b".to_string(), Integer(2));
-    equivalent! {
-        Foo2 { a_b: 2 },
-        Table(m),
-    }
-}
-
-#[test]
-fn nested() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a: isize,
-        b: Bar,
-    }
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Bar {
-        a: String,
-    }
-
-    equivalent! {
-        Foo { a: 2, b: Bar { a: "test".to_string() } },
-        Table(map! {
-            a: Integer(2),
-            b: Table(map! {
-                a: Value::String("test".to_string())
-            })
-        }),
-    }
-}
-
-#[test]
-fn array() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a: Vec<isize>,
-    }
-
-    equivalent! {
-        Foo { a: vec![1, 2, 3, 4] },
-        Table(map! {
-            a: Array(vec![
-                Integer(1),
-                Integer(2),
-                Integer(3),
-                Integer(4)
-            ])
-        }),
-    };
-}
-
-#[test]
-fn inner_structs_with_options() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a: Option<Box<Foo>>,
-        b: Bar,
-    }
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Bar {
-        a: String,
-        b: f64,
-    }
-
-    equivalent! {
-        Foo {
-            a: Some(Box::new(Foo {
-                a: None,
-                b: Bar { a: "foo".to_string(), b: 4.5 },
-            })),
-            b: Bar { a: "bar".to_string(), b: 1.0 },
-        },
-        Table(map! {
-            a: Table(map! {
-                b: Table(map! {
-                    a: Value::String("foo".to_string()),
-                    b: Float(4.5)
-                })
-            }),
-            b: Table(map! {
-                a: Value::String("bar".to_string()),
-                b: Float(1.0)
-            })
-        }),
-    }
-}
-
-#[test]
-fn table_array() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a: Vec<Bar>,
-    }
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Bar {
-        a: isize,
-    }
-
-    equivalent! {
-        Foo { a: vec![Bar { a: 1 }, Bar { a: 2 }] },
-        Table(map! {
-            a: Array(vec![
-                Table(map!{ a: Integer(1) }),
-                Table(map!{ a: Integer(2) }),
-            ])
-        }),
-    }
-}
-
-#[test]
-fn type_errors() {
-    #[derive(Deserialize)]
-    #[allow(dead_code)]
-    struct Foo {
-        bar: isize,
-    }
-
-    error! {
-        Foo,
-        Table(map! {
-            bar: Value::String("a".to_string())
-        }),
-        r#"TOML parse error at line 1, column 7
-  |
-1 | bar = "a"
-  |       ^^^
-invalid type: string "a", expected isize
-"#,
-        "invalid type: string \"a\", expected isize\n"
-    }
-
-    #[derive(Deserialize)]
-    #[allow(dead_code)]
-    struct Bar {
-        foo: Foo,
-    }
-
-    error! {
-        Bar,
-        Table(map! {
-            foo: Table(map! {
-                bar: Value::String("a".to_string())
-            })
-        }),
-        r#"TOML parse error at line 2, column 7
-  |
-2 | bar = "a"
-  |       ^^^
-invalid type: string "a", expected isize
-"#,
-        "invalid type: string \"a\", expected isize\n"
-    }
-}
-
-#[test]
-fn missing_errors() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug)]
-    struct Foo {
-        bar: isize,
-    }
-
-    error! {
-        Foo,
-        Table(map! { }),
-        r#"TOML parse error at line 1, column 1
-  |
-1 | 
-  | ^
-missing field `bar`
-"#,
-        "missing field `bar`\n"
-    }
-}
-
-#[test]
-fn parse_enum() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a: E,
-    }
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    #[serde(untagged)]
-    enum E {
-        Bar(isize),
-        Baz(String),
-        Last(Foo2),
-    }
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo2 {
-        test: String,
-    }
-
-    equivalent! {
-        Foo { a: E::Bar(10) },
-        Table(map! { a: Integer(10) }),
-    }
-
-    equivalent! {
-        Foo { a: E::Baz("foo".to_string()) },
-        Table(map! { a: Value::String("foo".to_string()) }),
-    }
-
-    equivalent! {
-        Foo { a: E::Last(Foo2 { test: "test".to_string() }) },
-        Table(map! { a: Table(map! { test: Value::String("test".to_string()) }) }),
-    }
-}
-
-#[test]
-fn parse_enum_string() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a: Sort,
-    }
-
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    #[serde(rename_all = "lowercase")]
-    enum Sort {
-        Asc,
-        Desc,
-    }
-
-    equivalent! {
-        Foo { a: Sort::Desc },
-        Table(map! { a: Value::String("desc".to_string()) }),
-    }
-}
-
-#[test]
-fn map_key_unit_variants() {
-    #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, PartialOrd, Ord)]
-    enum Sort {
-        #[serde(rename = "ascending")]
-        Asc,
-        Desc,
-    }
-
-    let mut map = BTreeMap::new();
-    map.insert(Sort::Asc, 1);
-    map.insert(Sort::Desc, 2);
-
-    equivalent! {
-        map,
-        Table(map! { ascending: Integer(1), Desc: Integer(2) }),
-    }
-}
-
-#[test]
-fn empty_arrays() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a: Vec<Bar>,
-    }
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Bar;
-
-    equivalent! {
-        Foo { a: vec![] },
-        Table(map! {a: Array(Vec::new())}),
-    }
-}
-
-#[test]
-fn empty_arrays2() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a: Option<Vec<Bar>>,
-    }
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Bar;
-
-    equivalent! {
-        Foo { a: None },
-        Table(map! {}),
-    }
-
-    equivalent! {
-        Foo { a: Some(vec![]) },
-        Table(map! { a: Array(vec![]) }),
-    }
-}
-
-#[test]
-fn extra_keys() {
-    #[derive(Serialize, Deserialize)]
-    struct Foo {
-        a: isize,
-    }
-
-    let toml = Table(map! { a: Integer(2), b: Integer(2) });
-    assert!(toml.clone().try_into::<Foo>().is_ok());
-    assert!(toml_edit::de::from_str::<Foo>(&toml.to_string()).is_ok());
-}
-
-#[test]
-fn newtypes() {
-    #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
-    struct A {
-        b: B,
-    }
-
-    #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
-    struct B(u32);
-
-    equivalent! {
-        A { b: B(2) },
-        Table(map! { b: Integer(2) }),
-    }
-}
-
-#[test]
-fn newtypes2() {
-    #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
-    struct A {
-        b: B,
-    }
-
-    #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
-    struct B(Option<C>);
-
-    #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
-    struct C {
-        x: u32,
-        y: u32,
-        z: u32,
-    }
-
-    equivalent! {
-        A { b: B(Some(C { x: 0, y: 1, z: 2 })) },
-        Table(map! {
-            b: Table(map! {
-                x: Integer(0),
-                y: Integer(1),
-                z: Integer(2)
-            })
-        }),
-    }
-}
-
-#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
-struct CanBeEmpty {
-    a: Option<String>,
-    b: Option<String>,
-}
-
-#[test]
-fn table_structs_empty() {
-    let text = "bar = {}\nbaz = {}\nbazv = { a = \"foo\" }\nfoo = {}\n";
-    let value: BTreeMap<String, CanBeEmpty> = toml_edit::de::from_str(text).unwrap();
-    let mut expected: BTreeMap<String, CanBeEmpty> = BTreeMap::new();
-    expected.insert("bar".to_string(), CanBeEmpty::default());
-    expected.insert("baz".to_string(), CanBeEmpty::default());
-    expected.insert(
-        "bazv".to_string(),
-        CanBeEmpty {
-            a: Some("foo".to_string()),
-            b: None,
-        },
-    );
-    expected.insert("foo".to_string(), CanBeEmpty::default());
-    assert_eq!(expected, value);
-    snapbox::assert_eq(text, toml_edit::ser::to_string(&value).unwrap());
-}
-
-#[test]
-fn fixed_size_array() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Entity {
-        pos: [i32; 2],
-    }
-
-    equivalent! {
-        Entity { pos: [1, 2] },
-        Table(map! {
-            pos: Array(vec![
-                Integer(1),
-                Integer(2),
-            ])
-        }),
-    }
-}
-
-#[test]
-fn homogeneous_tuple() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Collection {
-        elems: (i64, i64, i64),
-    }
-
-    equivalent! {
-        Collection { elems: (0, 1, 2) },
-        Table(map! {
-            elems: Array(vec![
-                Integer(0),
-                Integer(1),
-                Integer(2),
-            ])
-        }),
-    }
-}
-
-#[test]
-fn json_interoperability() {
-    #[derive(Serialize, Deserialize)]
-    struct Foo {
-        any: toml_edit::easy::Value,
-    }
-
-    let _foo: Foo = serde_json::from_str(
-        r#"
-        {"any":1}
-    "#,
-    )
-    .unwrap();
-}
-
-#[test]
-fn error_includes_key() {
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Package {
-        name: String,
-        version: String,
-        authors: Vec<String>,
-        profile: Profile,
-    }
-
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Profile {
-        dev: Dev,
-    }
-
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Dev {
-        debug: U32OrBool,
-    }
-
-    #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-    #[serde(untagged, expecting = "expected a boolean or an integer")]
-    pub enum U32OrBool {
-        U32(u32),
-        Bool(bool),
-    }
-
-    let res: Result<Package, _> = toml_edit::de::from_str(
-        r#"
-[package]
-name = "foo"
-version = "0.0.0"
-authors = []
-
-[profile.dev]
-debug = 'a'
-"#,
-    );
-    let err = res.unwrap_err();
-    snapbox::assert_eq(
-        r#"TOML parse error at line 8, column 9
-  |
-8 | debug = 'a'
-  |         ^^^
-expected a boolean or an integer
-"#,
-        err.to_string(),
-    );
-
-    let res: Result<Package, _> = toml_edit::de::from_str(
-        r#"
-[package]
-name = "foo"
-version = "0.0.0"
-authors = []
-
-[profile]
-dev = { debug = 'a' }
-"#,
-    );
-    let err = res.unwrap_err();
-    snapbox::assert_eq(
-        r#"TOML parse error at line 8, column 17
-  |
-8 | dev = { debug = 'a' }
-  |                 ^^^
-expected a boolean or an integer
-"#,
-        err.to_string(),
-    );
-}
-
-#[test]
-fn newline_key_value() {
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Package {
-        name: String,
-    }
-
-    let package = Package {
-        name: "foo".to_owned(),
-    };
-    let raw = toml_edit::ser::to_string_pretty(&package).unwrap();
-    assert_eq(
-        r#"name = "foo"
-"#,
-        raw,
-    );
-}
-
-#[test]
-fn newline_table() {
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Manifest {
-        package: Package,
-    }
-
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Package {
-        name: String,
-    }
-
-    let package = Manifest {
-        package: Package {
-            name: "foo".to_owned(),
-        },
-    };
-    let raw = toml_edit::ser::to_string_pretty(&package).unwrap();
-    assert_eq(
-        r#"[package]
-name = "foo"
-"#,
-        raw,
-    );
-}
-
-#[test]
-fn newline_dotted_table() {
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Manifest {
-        profile: Profile,
-    }
-
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Profile {
-        dev: Dev,
-    }
-
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Dev {
-        debug: U32OrBool,
-    }
-
-    #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-    #[serde(untagged, expecting = "expected a boolean or an integer")]
-    pub enum U32OrBool {
-        U32(u32),
-        Bool(bool),
-    }
-
-    let package = Manifest {
-        profile: Profile {
-            dev: Dev {
-                debug: U32OrBool::Bool(true),
-            },
-        },
-    };
-    let raw = toml_edit::ser::to_string_pretty(&package).unwrap();
-    assert_eq(
-        r#"[profile.dev]
-debug = true
-"#,
-        raw,
-    );
-}
-
-#[test]
-fn newline_mixed_tables() {
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Manifest {
-        cargo_features: Vec<String>,
-        package: Package,
-        profile: Profile,
-    }
-
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Package {
-        name: String,
-        version: String,
-        authors: Vec<String>,
-    }
-
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Profile {
-        dev: Dev,
-    }
-
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Dev {
-        debug: U32OrBool,
-    }
-
-    #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-    #[serde(untagged, expecting = "expected a boolean or an integer")]
-    pub enum U32OrBool {
-        U32(u32),
-        Bool(bool),
-    }
-
-    let package = Manifest {
-        cargo_features: vec![],
-        package: Package {
-            name: "foo".to_owned(),
-            version: "1.0.0".to_owned(),
-            authors: vec![],
-        },
-        profile: Profile {
-            dev: Dev {
-                debug: U32OrBool::Bool(true),
-            },
-        },
-    };
-    let raw = toml_edit::ser::to_string_pretty(&package).unwrap();
-    assert_eq(
-        r#"cargo_features = []
-
-[package]
-name = "foo"
-version = "1.0.0"
-authors = []
-
-[profile.dev]
-debug = true
-"#,
-        raw,
-    );
-}
-
-#[test]
-fn integer_min() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a_b: i64,
-    }
-
-    equivalent! {
-        Foo { a_b: i64::MIN },
-        Table(map! { a_b: Integer(i64::MIN) }),
-    }
-}
-
-#[test]
-fn integer_max() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a_b: i64,
-    }
-
-    equivalent! {
-        Foo { a_b: i64::MAX },
-        Table(map! { a_b: Integer(i64::MAX) }),
-    }
-}
-
-#[test]
-fn float_min() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a_b: f64,
-    }
-
-    equivalent! {
-        Foo { a_b: f64::MIN },
-        Table(map! { a_b: Float(f64::MIN) }),
-    }
-}
-
-#[test]
-fn float_max() {
-    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
-    struct Foo {
-        a_b: f64,
-    }
-
-    equivalent! {
-        Foo { a_b: f64::MAX },
-        Table(map! { a_b: Float(f64::MAX) }),
-    }
-}
-
-#[test]
-fn unsupported_root_type() {
-    let native = "value";
-    let err = toml_edit::ser::to_string_pretty(&native).unwrap_err();
-    snapbox::assert_eq("unsupported rust type", err.to_string());
-}
-
-#[test]
-fn unsupported_nested_type() {
-    #[derive(Debug, Serialize, Deserialize)]
-    struct Foo {
-        unused: (),
-    }
-
-    let native = Foo { unused: () };
-    let err = toml_edit::ser::to_string_pretty(&native).unwrap_err();
-    snapbox::assert_eq("unsupported unit type", err.to_string());
-}
diff --git a/crates/toml_edit/tests/testsuite/spanned.rs b/crates/toml_edit/tests/testsuite/spanned.rs
deleted file mode 100644
index 1359f4bf..00000000
--- a/crates/toml_edit/tests/testsuite/spanned.rs
+++ /dev/null
@@ -1,237 +0,0 @@
-#![cfg(feature = "easy")]
-#![allow(renamed_and_removed_lints)]
-#![allow(clippy::blacklisted_name)]
-
-use std::collections::HashMap;
-use std::fmt::Debug;
-
-use toml_edit::easy as toml;
-
-use serde::Deserialize;
-use toml_edit::easy::value::Datetime;
-use toml_edit::easy::Spanned;
-
-/// A set of good datetimes.
-pub fn good_datetimes() -> Vec<&'static str> {
-    vec![
-        "1997-09-09T09:09:09Z",
-        "1997-09-09T09:09:09+09:09",
-        "1997-09-09T09:09:09-09:09",
-        "1997-09-09T09:09:09",
-        "1997-09-09",
-        "09:09:09",
-        "1997-09-09T09:09:09.09Z",
-        "1997-09-09T09:09:09.09+09:09",
-        "1997-09-09T09:09:09.09-09:09",
-        "1997-09-09T09:09:09.09",
-        "09:09:09.09",
-    ]
-}
-
-#[test]
-fn test_spanned_field() {
-    #[derive(Deserialize)]
-    struct Foo<T> {
-        foo: Spanned<T>,
-    }
-
-    #[derive(Deserialize)]
-    struct BareFoo<T> {
-        foo: T,
-    }
-
-    fn good<T>(s: &str, expected: &str, end: Option<usize>)
-    where
-        T: serde::de::DeserializeOwned + Debug + PartialEq,
-    {
-        let foo: Foo<T> = toml::from_str(s).unwrap();
-
-        assert_eq!(6, foo.foo.span().start);
-        if let Some(end) = end {
-            assert_eq!(end, foo.foo.span().end);
-        } else {
-            assert_eq!(s.len(), foo.foo.span().end);
-        }
-        assert_eq!(expected, &s[foo.foo.span()]);
-
-        // Test for Spanned<> at the top level
-        let foo_outer: Spanned<BareFoo<T>> = toml::from_str(s).unwrap();
-
-        assert_eq!(0, foo_outer.span().start);
-        assert_eq!(s.len(), foo_outer.span().end);
-        assert_eq!(foo.foo.into_inner(), foo_outer.into_inner().foo);
-    }
-
-    good::<String>("foo = \"foo\"", "\"foo\"", None);
-    good::<u32>("foo = 42", "42", None);
-    // leading plus
-    good::<u32>("foo = +42", "+42", None);
-    // table
-    good::<HashMap<String, u32>>(
-        "foo = {\"foo\" = 42, \"bar\" = 42}",
-        "{\"foo\" = 42, \"bar\" = 42}",
-        None,
-    );
-    // array
-    good::<Vec<u32>>("foo = [0, 1, 2, 3, 4]", "[0, 1, 2, 3, 4]", None);
-    // datetime
-    good::<String>(
-        "foo = \"1997-09-09T09:09:09Z\"",
-        "\"1997-09-09T09:09:09Z\"",
-        None,
-    );
-
-    for expected in good_datetimes() {
-        let s = format!("foo = {}", expected);
-        good::<Datetime>(&s, expected, None);
-    }
-    // ending at something other than the absolute end
-    good::<u32>("foo = 42\nnoise = true", "42", Some(8));
-}
-
-#[test]
-fn test_inner_spanned_table() {
-    #[derive(Deserialize)]
-    struct Foo {
-        foo: Spanned<HashMap<Spanned<String>, Spanned<String>>>,
-    }
-
-    fn good(s: &str, zero: bool) {
-        let foo: Foo = toml::from_str(s).unwrap();
-
-        if zero {
-            assert_eq!(foo.foo.span().start, 0);
-            assert_eq!(foo.foo.span().end, 73);
-        } else {
-            assert_eq!(foo.foo.span().start, s.find('{').unwrap());
-            assert_eq!(foo.foo.span().end, s.find('}').unwrap() + 1);
-        }
-        for (k, v) in foo.foo.as_ref().iter() {
-            assert_eq!(&s[k.span().start..k.span().end], k.as_ref());
-            assert_eq!(&s[(v.span().start + 1)..(v.span().end - 1)], v.as_ref());
-        }
-    }
-
-    good(
-        "\
-        [foo]
-        a = 'b'
-        bar = 'baz'
-        c = 'd'
-        e = \"f\"
-    ",
-        true,
-    );
-
-    good(
-        "
-        foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }",
-        false,
-    );
-}
-
-#[test]
-fn test_outer_spanned_table() {
-    #[derive(Deserialize)]
-    struct Foo {
-        foo: HashMap<Spanned<String>, Spanned<String>>,
-    }
-
-    fn good(s: &str) {
-        let foo: Foo = toml::from_str(s).unwrap();
-
-        for (k, v) in foo.foo.iter() {
-            assert_eq!(&s[k.span().start..k.span().end], k.as_ref());
-            assert_eq!(&s[(v.span().start + 1)..(v.span().end - 1)], v.as_ref());
-        }
-    }
-
-    good(
-        "
-        [foo]
-        a = 'b'
-        bar = 'baz'
-        c = 'd'
-        e = \"f\"
-    ",
-    );
-
-    good(
-        "
-        foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }
-    ",
-    );
-}
-
-#[test]
-fn test_spanned_nested() {
-    #[derive(Deserialize)]
-    struct Foo {
-        foo: HashMap<Spanned<String>, HashMap<Spanned<String>, Spanned<String>>>,
-    }
-
-    fn good(s: &str) {
-        let foo: Foo = toml::from_str(s).unwrap();
-
-        for (k, v) in foo.foo.iter() {
-            assert_eq!(&s[k.span().start..k.span().end], k.as_ref());
-            for (n_k, n_v) in v.iter() {
-                assert_eq!(&s[n_k.span().start..n_k.span().end], n_k.as_ref());
-                assert_eq!(
-                    &s[(n_v.span().start + 1)..(n_v.span().end - 1)],
-                    n_v.as_ref()
-                );
-            }
-        }
-    }
-
-    good(
-        "
-        [foo.a]
-        a = 'b'
-        c = 'd'
-        e = \"f\"
-        [foo.bar]
-        baz = 'true'
-    ",
-    );
-
-    good(
-        "
-        [foo]
-        foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }
-        bazz = {}
-        g = { h = 'i' }
-    ",
-    );
-}
-
-#[test]
-fn test_spanned_array() {
-    #[derive(Deserialize)]
-    struct Foo {
-        foo: Vec<Spanned<HashMap<Spanned<String>, Spanned<String>>>>,
-    }
-
-    let toml = "\
-        [[foo]]
-        a = 'b'
-        bar = 'baz'
-        c = 'd'
-        e = \"f\"
-        [[foo]]
-        a = 'c'
-        bar = 'baz'
-        c = 'g'
-        e = \"h\"
-    ";
-    let foo_list: Foo = toml::from_str(toml).unwrap();
-
-    for (foo, expected) in foo_list.foo.iter().zip([0..75, 84..159]) {
-        assert_eq!(foo.span(), expected);
-        for (k, v) in foo.as_ref().iter() {
-            assert_eq!(&toml[k.span().start..k.span().end], k.as_ref());
-            assert_eq!(&toml[(v.span().start + 1)..(v.span().end - 1)], v.as_ref());
-        }
-    }
-}
diff --git a/crates/toml_edit/tests/testsuite/spanned_impls.rs b/crates/toml_edit/tests/testsuite/spanned_impls.rs
deleted file mode 100644
index ceb21991..00000000
--- a/crates/toml_edit/tests/testsuite/spanned_impls.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-#![cfg(feature = "easy")]
-
-use std::cmp::{Ord, Ordering, PartialOrd};
-
-use serde::Deserialize;
-use toml_edit::easy::{from_str, Spanned};
-
-#[test]
-fn test_spans_impls() {
-    #[derive(Deserialize)]
-    struct Foo {
-        bar: Spanned<bool>,
-        baz: Spanned<String>,
-    }
-    let f: Foo = from_str(
-        "
-    bar = true
-    baz = \"yes\"
-    ",
-    )
-    .unwrap();
-    let g: Foo = from_str(
-        "
-    baz = \"yes\"
-    bar = true
-    ",
-    )
-    .unwrap();
-    assert!(f.bar.span() != g.bar.span());
-    assert!(f.baz.span() != g.baz.span());
-
-    // test that eq still holds
-    assert_eq!(f.bar, g.bar);
-    assert_eq!(f.baz, g.baz);
-
-    // test that Ord returns equal order
-    assert_eq!(f.bar.cmp(&g.bar), Ordering::Equal);
-    assert_eq!(f.baz.cmp(&g.baz), Ordering::Equal);
-
-    // test that PartialOrd returns equal order
-    assert_eq!(f.bar.partial_cmp(&g.bar), Some(Ordering::Equal));
-    assert_eq!(f.baz.partial_cmp(&g.baz), Some(Ordering::Equal));
-}