4
0
Эх сурвалжийг харах

Rename features and display in version

• Give all Cargo features a naming scheme where they start with `with_`
• Change the build script so that excluded features are now present in the version string (included features are not shown, so by default the string does not change)
• Document this in the READMEs
Benjamin Sago 4 жил өмнө
parent
commit
3282eaf79f

+ 4 - 4
Cargo.toml

@@ -69,7 +69,7 @@ datetime = { version = "0.5.1", default_features = false }
 pretty_assertions = "0.7"
 
 [features]
-default = ["idna", "tls", "https"]
-idna = ["dns/idna"]
-tls = ["dns-transport/tls"]
-https = ["dns-transport/https"]
+default = ["with_idna", "with_tls", "with_https"]
+with_idna = ["dns/with_idna"]
+with_tls = ["dns-transport/with_tls"]
+with_https = ["dns-transport/with_https"]

+ 8 - 2
Justfile

@@ -60,12 +60,12 @@ export DOG_DEBUG := ""
 
 # run extended tests
 @xtests *args:
-    specsheet xtests/*/*.toml -shide {{args}} \
+    specsheet xtests/{live,madns,options}/*.toml -shide {{args}} \
         -O cmd.target.dog="${CARGO_TARGET_DIR:-../../target}/debug/dog"
 
 # run extended tests (in release mode)
 @xtests-release *args:
-    specsheet xtests/*/*.toml {{args}} \
+    specsheet xtests/{live,madns,options}/*.toml {{args}} \
         -O cmd.target.dog="${CARGO_TARGET_DIR:-../../target}/release/dog"
 
 # run extended tests (omitting certain feature tests)
@@ -120,6 +120,12 @@ export DOG_DEBUG := ""
     command -v cargo-udeps >/dev/null || (echo "cargo-udeps not installed" && exit 1)
     cargo +nightly udeps
 
+# builds dog and runs extended tests with features disabled
+@feature-checks *args:
+    cargo build --no-default-features
+    specsheet xtests/features/none.toml -shide {{args}} \
+        -O cmd.target.dog="${CARGO_TARGET_DIR:-../../target}/debug/dog"
+
 # print versions of the necessary build tools
 @versions:
     rustc --version

+ 22 - 2
README.md

@@ -104,6 +104,7 @@ The `just man` command will compile the Markdown into manual pages, which it wil
 To use them, copy them into a directory that `man` will read.
 `/usr/local/share/man` is usually a good choice.
 
+
 ### Container image
 
 To build the container image of dog, you can use Docker or Kaniko. Here an example using Docker:
@@ -118,18 +119,37 @@ To run dog directly, you can then define the following alias:
 
     $ alias dog="docker run -it --rm dog"
 
-### Testing
+
+### End-to-end testing
 
 dog has an integration test suite written as [Specsheet](https://specsheet.software/) check documents.
 If you have a copy installed, you can run:
 
-    just xtests
+    $ just xtests
 
 Specsheet will test the compiled binary by making DNS requests over the network, checking that dog returns the correct results and does not crash.
 Note that this will expose your IP address.
 For more information, read [the xtests README](xtests/README.md).
 
 
+### Feature toggles
+
+dog has three Cargo features that can be switched off to remove functionality.
+While doing so makes dog less useful, it results in a smaller binary that takes less time to build.
+
+There are three feature toggles available, all of which are active by default:
+
+- `with_idna`, which enables [IDNA](https://en.wikipedia.org/wiki/Internationalized_domain_name) processing
+- `with_tls`, which enables DNS-over-TLS
+- `with_https`, which enables DNS-over-HTTPS (requires `with_tls`)
+
+Use `cargo` to build a binary that uses feature toggles. For example, to disable TLS and HTTPS support but keep IDNA support enabled, you can run:
+
+    $ cargo build --no-default-features --features=with_idna
+
+The list of features that have been disabled can be checked at runtime as part of the `--version` string.
+
+
 ---
 
 ## Documentation

+ 42 - 3
build.rs

@@ -31,13 +31,13 @@ fn main() -> io::Result<()> {
 
     let ver =
         if is_debug_build() {
-            format!("{}\nv{} \\1;31m(pre-release debug build!)\\0m\n\\1;4;34m{}\\0m", tagline, cargo_version(), url)
+            format!("{}\nv{} \\1;31m(pre-release debug build!)\\0m\n\\1;4;34m{}\\0m", tagline, version_string(), url)
         }
         else if is_development_version() {
-            format!("{}\nv{} [{}] built on {} \\1;31m(pre-release!)\\0m\n\\1;4;34m{}\\0m", tagline, cargo_version(), git_hash(), build_date(), url)
+            format!("{}\nv{} [{}] built on {} \\1;31m(pre-release!)\\0m\n\\1;4;34m{}\\0m", tagline, version_string(), git_hash(), build_date(), url)
         }
         else {
-            format!("{}\nv{}\n\\1;4;34m{}\\0m", tagline, cargo_version(), url)
+            format!("{}\nv{}\n\\1;4;34m{}\\0m", tagline, version_string(), url)
         };
 
     // We need to create these files in the Cargo output directory.
@@ -113,6 +113,45 @@ fn cargo_version() -> String {
     env::var("CARGO_PKG_VERSION").unwrap()
 }
 
+/// Returns the version and build parameters string.
+fn version_string() -> String {
+    let mut ver = cargo_version();
+
+    let feats = nonstandard_features_string();
+    if ! feats.is_empty() {
+        ver.push_str(&format!(" [{}]", &feats));
+    }
+
+    ver
+}
+
+/// Finds whether a feature is enabled by examining the Cargo variable.
+fn feature_enabled(name: &str) -> bool {
+    env::var(&format!("CARGO_FEATURE_{}", name))
+        .map(|e| ! e.is_empty())
+        .unwrap_or(false)
+}
+
+/// A comma-separated list of non-standard feature choices.
+fn nonstandard_features_string() -> String {
+    let mut s = Vec::new();
+
+    if ! feature_enabled("WITH_IDNA") {
+        s.push("-idna");
+    }
+
+    if ! feature_enabled("WITH_TLS") {
+        s.push("-tls");
+    }
+
+    if ! feature_enabled("WITH_HTTPS") {
+        s.push("-https");
+    }
+
+    s.join(", ")
+}
+
+
 /// Formats the current date as an ISO 8601 string.
 fn build_date() -> String {
     let now = LocalDateTime::now();

+ 2 - 2
dns-transport/Cargo.toml

@@ -25,5 +25,5 @@ httparse = { version = "1.3", optional = true }
 
 [features]
 default = []  # these are enabled in the main dog crate
-tls   = ["native-tls"]
-https = ["native-tls", "httparse"]
+with_tls   = ["native-tls"]
+with_https = ["native-tls", "httparse"]

+ 7 - 7
dns-transport/src/error.rs

@@ -14,20 +14,20 @@ pub enum Error {
     TruncatedResponse,
 
     /// There was a problem making a TLS request.
-    #[cfg(feature="tls")]
+    #[cfg(feature = "with_tls")]
     TlsError(native_tls::Error),
 
     /// There was a problem _establishing_ a TLS request.
-    #[cfg(feature="tls")]
+    #[cfg(feature = "with_tls")]
     TlsHandshakeError(native_tls::HandshakeError<std::net::TcpStream>),
 
     /// There was a problem decoding the response HTTP headers or body.
-    #[cfg(feature="https")]
+    #[cfg(feature = "with_https")]
     HttpError(httparse::Error),
 
     /// The HTTP response code was something other than 200 OK, along with the
     /// response code text, if present.
-    #[cfg(feature="https")]
+    #[cfg(feature = "with_https")]
     WrongHttpStatus(u16, Option<String>),
 }
 
@@ -46,21 +46,21 @@ impl From<std::io::Error> for Error {
     }
 }
 
-#[cfg(feature="tls")]
+#[cfg(feature = "with_tls")]
 impl From<native_tls::Error> for Error {
     fn from(inner: native_tls::Error) -> Self {
         Self::TlsError(inner)
     }
 }
 
-#[cfg(feature="tls")]
+#[cfg(feature = "with_tls")]
 impl From<native_tls::HandshakeError<std::net::TcpStream>> for Error {
     fn from(inner: native_tls::HandshakeError<std::net::TcpStream>) -> Self {
         Self::TlsHandshakeError(inner)
     }
 }
 
-#[cfg(feature="https")]
+#[cfg(feature = "with_https")]
 impl From<httparse::Error> for Error {
     fn from(inner: httparse::Error) -> Self {
         Self::HttpError(inner)

+ 3 - 3
dns-transport/src/https.rs

@@ -1,4 +1,4 @@
-#![cfg_attr(not(feature="https"), allow(unused))]
+#![cfg_attr(not(feature = "https"), allow(unused))]
 
 use std::io::{Read, Write};
 use std::net::TcpStream;
@@ -25,7 +25,7 @@ impl HttpsTransport {
 
 impl Transport for HttpsTransport {
 
-    #[cfg(feature="https")]
+    #[cfg(feature = "with_https")]
     fn send(&self, request: &Request) -> Result<Response, Error> {
         let connector = native_tls::TlsConnector::new()?;
 
@@ -75,7 +75,7 @@ impl Transport for HttpsTransport {
         Ok(response)
     }
 
-    #[cfg(not(feature="https"))]
+    #[cfg(not(feature = "with_https"))]
     fn send(&self, request: &Request) -> Result<Response, Error> {
         unimplemented!("HTTPS feature disabled")
     }

+ 3 - 3
dns-transport/src/tls.rs

@@ -1,4 +1,4 @@
-#![cfg_attr(not(feature="tls"), allow(unused))]
+#![cfg_attr(not(feature = "tls"), allow(unused))]
 
 use std::net::TcpStream;
 use std::io::Write;
@@ -26,7 +26,7 @@ impl TlsTransport {
 
 impl Transport for TlsTransport {
 
-    #[cfg(feature="tls")]
+    #[cfg(feature = "with_tls")]
     fn send(&self, request: &Request) -> Result<Response, Error> {
         let connector = native_tls::TlsConnector::new()?;
 
@@ -58,7 +58,7 @@ impl Transport for TlsTransport {
         Ok(response)
     }
 
-    #[cfg(not(feature="tls"))]
+    #[cfg(not(feature = "with_tls"))]
     fn send(&self, request: &Request) -> Result<Response, Error> {
         unimplemented!("TLS feature disabled")
     }

+ 5 - 5
dns/Cargo.toml

@@ -13,22 +13,22 @@ doctest = false
 # logging
 log = "0.4"
 
-# protocol parsing
+# protocol parsing helper
 byteorder = "1.3"
 
-# packet printing
+# printing of certain packets
 base64 = "0.13"
 
 # idna encoding
 unic-idna = { version = "0.9.0", optional = true }
 
-# testing
+# mutation testing
 mutagen = { git = "https://github.com/llogiq/mutagen", optional = true }
 
 [dev-dependencies]
 pretty_assertions = "0.7"
 
 [features]
-default = []  # these are enabled in the main dog crate
-idna = ["unic-idna"]
+default = []  # idna is enabled in the main dog crate
+with_idna = ["unic-idna"]
 with_mutagen = ["mutagen"]  # needs nightly

+ 2 - 2
dns/src/strings.rs

@@ -20,13 +20,13 @@ pub struct Labels {
     segments: Vec<(u8, String)>,
 }
 
-#[cfg(feature = "idna")]
+#[cfg(feature = "with_idna")]
 fn label_to_ascii(label: &str) -> Result<String, unic_idna::Errors> {
     let flags = unic_idna::Flags{use_std3_ascii_rules: true, transitional_processing: false, verify_dns_length: true};
     unic_idna::to_ascii(label, flags)
 }
 
-#[cfg(not(feature = "idna"))]
+#[cfg(not(feature = "with_idna"))]
 fn label_to_ascii(label: &str) -> Result<String, ()> {
     Ok(label.to_owned())
 }

+ 6 - 6
src/output.rs

@@ -609,10 +609,10 @@ fn erroneous_phase(error: &TransportError) -> &'static str {
         TransportError::WireError(_)          => "protocol",
         TransportError::TruncatedResponse     |
         TransportError::NetworkError(_)       => "network",
-        #[cfg(feature="tls")]
+        #[cfg(feature = "with_tls")]
         TransportError::TlsError(_)           |
         TransportError::TlsHandshakeError(_)  => "tls",
-        #[cfg(feature="https")]
+        #[cfg(feature = "with_https")]
         TransportError::HttpError(_)          |
         TransportError::WrongHttpStatus(_,_)  => "http",
     }
@@ -624,13 +624,13 @@ fn error_message(error: TransportError) -> String {
         TransportError::WireError(e)          => wire_error_message(e),
         TransportError::TruncatedResponse     => "Truncated response".into(),
         TransportError::NetworkError(e)       => e.to_string(),
-        #[cfg(feature="tls")]
+        #[cfg(feature = "with_tls")]
         TransportError::TlsError(e)           => e.to_string(),
-        #[cfg(feature="tls")]
+        #[cfg(feature = "with_tls")]
         TransportError::TlsHandshakeError(e)  => e.to_string(),
-        #[cfg(feature="https")]
+        #[cfg(feature = "with_https")]
         TransportError::HttpError(e)          => e.to_string(),
-        #[cfg(feature="https")]
+        #[cfg(feature = "with_https")]
         TransportError::WrongHttpStatus(t,r)  => format!("Nameserver returned HTTP {} ({})", t, r.unwrap_or_else(|| "No reason".into()))
     }
 }

+ 8 - 4
xtests/README.md

@@ -7,15 +7,19 @@ The checks are written as [Specsheet] documents, which you’ll need to have ins
 Because these tests make connections over the network, the outcome of the test suite will depend on your own machine‘s Internet connection! It also means that your own IP address will be recorded as making the requests.
 
 
-### Test hierarchy
+### Test layout
 
-The tests have been divided into three sections:
+The tests have been divided into four sections:
+
+1. **live**, which uses both your computer’s default resolver and the [public Cloudflare DNS resolver] to access records that have been created using a public-facing DNS host. This checks that dog works using whatever software is between you and those nameservers on the Internet right now. Because these are _live_ records, the output will vary as things like the TTL vary, so we cannot assert on the _exact_ output; nevertheless, it’s a good check to see if the basic functionality is working.
 
-1. **live**, which uses both your computer’s default resolver and the [public Cloudflare DNS resolver] to access records that have been created using a public-facing DNS host. This checks that dog works using whatever software is on the Internet right now. Because these are _live_ records, the output will vary as things like the TTL vary, so we cannot assert on the _exact_ output; nevertheless, it’s a good check to see if the basic functionality is working.
 2. **madns**, which sends requests to the [madns resolver]. This resolver has been pre-programmed with deliberately incorrect responses to see how dog handles edge cases in the DNS specification. These are not live records, so things like the TTLs of the responses are fixed, meaning the output should never change over time; however, it does not mean dog will hold up against the network infrastructure of the real world.
+
 3. **options**, which runs dog using various command-line options and checks that the correct output is returned. These tests should not make network requests when behaving correctly.
 
-All three categories of check are needed to ensure dog is working correctly.
+4. **features**, which checks dog does the right thing when certain features have been enabled or disabled at compile-time. These tests also should not make network requests when behaving correctly.
+
+All four categories of check are needed to ensure dog is working correctly.
 
 
 ### Tags

+ 11 - 0
xtests/features/none.toml

@@ -0,0 +1,11 @@
+# These tests are meant to be run against a dog binary compiled with
+# `--no-default-features`. They will fail otherwise.
+
+
+[[cmd]]
+name = "The missing features are documented in the version"
+shell = "dog --version"
+stdout = { string = "[-idna, -tls, -https]" }
+stderr = { empty = true }
+status = 0
+tags = [ 'features' ]