commit 3f58ea692f8e540b5d8c4ddc9e3fb1cda8eac0b3
parent 7dd0c8fd02c9c5faa03ec6ffd7b6c3cea0e73afc
Author: Sergio Benitez <sb@sergio.bz>
Date: Wed, 12 Dec 2018 00:00:10 -0800
Add compile tests to contrib codegen.
Diffstat:
8 files changed, 299 insertions(+), 23 deletions(-)
diff --git a/contrib/codegen/Cargo.toml b/contrib/codegen/Cargo.toml
@@ -24,3 +24,8 @@ quote = "0.6"
[build-dependencies]
yansi = "0.5"
version_check = "0.1.3"
+
+[dev-dependencies]
+compiletest_rs = { version = "0.3", features = ["stable"] }
+rocket = { version = "0.4.0", path = "../../core/lib" }
+rocket_contrib = { version = "0.4.0", path = "../lib", features = ["diesel_sqlite_pool"] }
diff --git a/contrib/codegen/src/database.rs b/contrib/codegen/src/database.rs
@@ -20,8 +20,8 @@ const EXAMPLE: &str = "example: `struct MyDatabase(diesel::SqliteConnection);`";
const ONLY_ON_STRUCTS_MSG: &str = "`database` attribute can only be used on structs";
const ONLY_UNNAMED_FIELDS: &str = "`database` attribute can only be applied to \
structs with exactly one unnamed field";
-const NO_GENERIC_STRUCTS: &str = "`database` attribute cannot be applied to a struct \
- with a generic type";
+const NO_GENERIC_STRUCTS: &str = "`database` attribute cannot be applied to structs \
+ with generics";
fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result<DatabaseInvocation> {
let attr_stream2 = ::proc_macro2::TokenStream::from(attr);
@@ -31,7 +31,7 @@ fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result<DatabaseInv
let input = ::syn::parse::<DeriveInput>(input).unwrap();
if !input.generics.params.is_empty() {
- return Err(input.span().error(NO_GENERIC_STRUCTS));
+ return Err(input.generics.span().error(NO_GENERIC_STRUCTS));
}
let structure = match input.data {
@@ -56,34 +56,37 @@ fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result<DatabaseInv
})
}
+#[allow(non_snake_case)]
pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStream> {
let invocation = parse_invocation(attr, input)?;
// Store everything we're going to need to generate code.
- let connection_type = &invocation.connection_type;
+ let conn_type = &invocation.connection_type;
let name = &invocation.db_name;
- let request_guard_type = &invocation.type_name;
+ let guard_type = &invocation.type_name;
let vis = &invocation.visibility;
- let pool_type = Ident::new(&format!("{}Pool", request_guard_type), request_guard_type.span());
+ let pool_type = Ident::new(&format!("{}Pool", guard_type), guard_type.span());
let fairing_name = format!("'{}' Database Pool", name);
+ let span = conn_type.span().into();
// A few useful paths.
- let databases = quote!(::rocket_contrib::databases);
- let r2d2 = quote!(#databases::r2d2);
+ let databases = quote_spanned!(span => ::rocket_contrib::databases);
+ let Poolable = quote_spanned!(span => #databases::Poolable);
+ let r2d2 = quote_spanned!(span => #databases::r2d2);
let request = quote!(::rocket::request);
- Ok(quote! {
+ let generated_types = quote_spanned! { span =>
/// The request guard type.
- #vis struct #request_guard_type(
- pub #r2d2::PooledConnection<<#connection_type as #databases::Poolable>::Manager>
- );
+ #vis struct #guard_type(pub #r2d2::PooledConnection<<#conn_type as #Poolable>::Manager>);
/// The pool type.
- #vis struct #pool_type(
- #r2d2::Pool<<#connection_type as #databases::Poolable>::Manager>
- );
+ #vis struct #pool_type(#r2d2::Pool<<#conn_type as #Poolable>::Manager>);
+ };
+
+ Ok(quote! {
+ #generated_types
- impl #request_guard_type {
+ impl #guard_type {
/// Returns a fairing that initializes the associated database
/// connection pool.
pub fn fairing() -> impl ::rocket::fairing::Fairing {
@@ -91,7 +94,7 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
::rocket::fairing::AdHoc::on_attach(#fairing_name, |rocket| {
let pool = #databases::database_config(#name, rocket.config())
- .map(<#connection_type>::pool);
+ .map(<#conn_type>::pool);
match pool {
Ok(Ok(p)) => Ok(rocket.manage(#pool_type(p))),
@@ -117,12 +120,12 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
pub fn get_one(rocket: &::rocket::Rocket) -> Option<Self> {
rocket.state::<#pool_type>()
.and_then(|pool| pool.0.get().ok())
- .map(#request_guard_type)
+ .map(#guard_type)
}
}
- impl ::std::ops::Deref for #request_guard_type {
- type Target = #connection_type;
+ impl ::std::ops::Deref for #guard_type {
+ type Target = #conn_type;
#[inline(always)]
fn deref(&self) -> &Self::Target {
@@ -130,14 +133,14 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
}
}
- impl ::std::ops::DerefMut for #request_guard_type {
+ impl ::std::ops::DerefMut for #guard_type {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
- impl<'a, 'r> #request::FromRequest<'a, 'r> for #request_guard_type {
+ impl<'a, 'r> #request::FromRequest<'a, 'r> for #guard_type {
type Error = ();
fn from_request(request: &'a #request::Request<'r>) -> #request::Outcome<Self, ()> {
@@ -145,7 +148,7 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
let pool = request.guard::<::rocket::State<#pool_type>>()?;
match pool.0.get() {
- Ok(conn) => Outcome::Success(#request_guard_type(conn)),
+ Ok(conn) => Outcome::Success(#guard_type(conn)),
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
}
}
diff --git a/contrib/codegen/tests/compile-test.rs b/contrib/codegen/tests/compile-test.rs
@@ -0,0 +1,102 @@
+extern crate compiletest_rs as compiletest;
+
+use std::path::{Path, PathBuf};
+use std::{io, fs::Metadata, time::SystemTime};
+
+#[derive(Copy, Clone)]
+enum Kind {
+ #[allow(dead_code)]
+ Dynamic,
+ Static
+}
+
+impl Kind {
+ fn extension(self) -> &'static str {
+ match self {
+ #[cfg(windows)] Kind::Dynamic => ".dll",
+ #[cfg(all(unix, target_os = "macos"))] Kind::Dynamic => ".dylib",
+ #[cfg(all(unix, not(target_os = "macos")))] Kind::Dynamic => ".so",
+ Kind::Static => ".rlib"
+ }
+ }
+}
+
+fn target_path() -> PathBuf {
+ #[cfg(debug_assertions)] const ENVIRONMENT: &str = "debug";
+ #[cfg(not(debug_assertions))] const ENVIRONMENT: &str = "release";
+
+ Path::new(env!("CARGO_MANIFEST_DIR"))
+ .parent().unwrap().parent().unwrap()
+ .join("target")
+ .join(ENVIRONMENT)
+}
+
+fn link_flag(flag: &str, lib: &str, rel_path: &[&str]) -> String {
+ let mut path = target_path();
+ for component in rel_path {
+ path = path.join(component);
+ }
+
+ format!("{} {}={}", flag, lib, path.display())
+}
+
+fn best_time_for(metadata: &Metadata) -> SystemTime {
+ metadata.created()
+ .or_else(|_| metadata.modified())
+ .or_else(|_| metadata.accessed())
+ .unwrap_or_else(|_| SystemTime::now())
+}
+
+fn extern_dep(name: &str, kind: Kind) -> io::Result<String> {
+ let deps_root = target_path().join("deps");
+ let dep_name = format!("lib{}", name);
+
+ let mut dep_path: Option<PathBuf> = None;
+ for entry in deps_root.read_dir().expect("read_dir call failed") {
+ let entry = match entry {
+ Ok(entry) => entry,
+ Err(_) => continue
+ };
+
+ let filename = entry.file_name();
+ let filename = filename.to_string_lossy();
+ let lib_name = filename.split('.').next().unwrap().split('-').next().unwrap();
+
+ if lib_name == dep_name && filename.ends_with(kind.extension()) {
+ if let Some(ref mut existing) = dep_path {
+ if best_time_for(&entry.metadata()?) > best_time_for(&existing.metadata()?) {
+ *existing = entry.path().into();
+ }
+ } else {
+ dep_path = Some(entry.path().into());
+ }
+ }
+ }
+
+ let dep = dep_path.ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?;
+ let filename = dep.file_name().ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
+ Ok(link_flag("--extern", name, &["deps", &filename.to_string_lossy()]))
+}
+
+fn run_mode(mode: &'static str, path: &'static str) {
+ let mut config = compiletest::Config::default();
+ config.mode = mode.parse().expect("invalid mode");
+ config.src_base = format!("tests/{}", path).into();
+ config.clean_rmeta();
+
+ config.target_rustcflags = Some([
+ link_flag("-L", "crate", &[]),
+ link_flag("-L", "dependency", &["deps"]),
+ extern_dep("rocket_http", Kind::Static).expect("find http dep"),
+ extern_dep("rocket", Kind::Static).expect("find core dep"),
+ extern_dep("rocket_contrib", Kind::Static).expect("find contrib dep"),
+ ].join(" "));
+
+ compiletest::run_tests(&config);
+}
+
+#[test]
+fn compile_test() {
+ run_mode("ui", "ui-fail");
+ run_mode("compile-fail", "ui-fail");
+}
diff --git a/contrib/codegen/tests/ui-fail/database-syntax.rs b/contrib/codegen/tests/ui-fail/database-syntax.rs
@@ -0,0 +1,41 @@
+#[macro_use] extern crate rocket_contrib;
+
+use rocket_contrib::databases::diesel;
+
+#[database]
+//~^ ERROR expected string literal
+struct A(diesel::SqliteConnection);
+
+#[database(1)]
+//~^ ERROR expected string literal
+struct B(diesel::SqliteConnection);
+
+#[database(123)]
+//~^ ERROR expected string literal
+struct C(diesel::SqliteConnection);
+
+#[database("hello" "hi")]
+//~^ ERROR expected string literal
+struct D(diesel::SqliteConnection);
+
+#[database("test")]
+enum Foo { }
+//~^ ERROR on structs
+
+#[database("test")]
+struct Bar(diesel::SqliteConnection, diesel::SqliteConnection);
+//~^ ERROR one unnamed field
+
+#[database("test")]
+union Baz { }
+//~^ ERROR on structs
+
+#[database("test")]
+struct E<'r>(&'r str);
+//~^ ERROR generics
+
+#[database("test")]
+struct F<T>(T);
+//~^ ERROR generics
+
+fn main() { }
diff --git a/contrib/codegen/tests/ui-fail/database-syntax.stderr b/contrib/codegen/tests/ui-fail/database-syntax.stderr
@@ -0,0 +1,58 @@
+error: expected string literal
+ --> $DIR/database-syntax.rs:5:1
+ |
+5 | #[database]
+ | ^^^^^^^^^^^
+
+error: expected string literal
+ --> $DIR/database-syntax.rs:9:12
+ |
+9 | #[database(1)]
+ | ^
+
+error: expected string literal
+ --> $DIR/database-syntax.rs:13:12
+ |
+13 | #[database(123)]
+ | ^^^
+
+error: expected string literal
+ --> $DIR/database-syntax.rs:17:12
+ |
+17 | #[database("hello" "hi")]
+ | ^^^^^^^^^^^^
+
+error: `database` attribute can only be used on structs
+ --> $DIR/database-syntax.rs:22:1
+ |
+22 | enum Foo { }
+ | ^^^^^^^^^^^^^
+
+error: `database` attribute can only be applied to structs with exactly one unnamed field
+ --> $DIR/database-syntax.rs:26:11
+ |
+26 | struct Bar(diesel::SqliteConnection, diesel::SqliteConnection);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: example: `struct MyDatabase(diesel::SqliteConnection);`
+
+error: `database` attribute can only be used on structs
+ --> $DIR/database-syntax.rs:30:1
+ |
+30 | union Baz { }
+ | ^^^^^^^^^^^^^^
+
+error: `database` attribute cannot be applied to structs with generics
+ --> $DIR/database-syntax.rs:34:9
+ |
+34 | struct E<'r>(&'r str);
+ | ^^^^
+
+error: `database` attribute cannot be applied to structs with generics
+ --> $DIR/database-syntax.rs:38:9
+ |
+38 | struct F<T>(T);
+ | ^^^
+
+error: aborting due to 9 previous errors
+
diff --git a/contrib/codegen/tests/ui-fail/database-types.rs b/contrib/codegen/tests/ui-fail/database-types.rs
@@ -0,0 +1,10 @@
+extern crate rocket;
+#[macro_use] extern crate rocket_contrib;
+
+struct Unknown;
+
+#[database("foo")]
+struct A(Unknown);
+//~^ ERROR Unknown: rocket_contrib::databases::Poolable
+
+fn main() { }
diff --git a/contrib/codegen/tests/ui-fail/database-types.stderr b/contrib/codegen/tests/ui-fail/database-types.stderr
@@ -0,0 +1,9 @@
+error[E0277]: the trait bound `Unknown: rocket_contrib::databases::Poolable` is not satisfied
+ --> $DIR/database-types.rs:7:10
+ |
+7 | struct A(Unknown);
+ | ^^^^^^^ the trait `rocket_contrib::databases::Poolable` is not implemented for `Unknown`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0277`.
diff --git a/contrib/codegen/tests/ui-fail/update-references.sh b/contrib/codegen/tests/ui-fail/update-references.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+#
+# Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+# file at the top-level directory of this distribution and at
+# http://rust-lang.org/COPYRIGHT.
+#
+# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+# option. This file may not be copied, modified, or distributed
+# except according to those terms.
+
+# A script to update the references for particular tests. The idea is
+# that you do a run, which will generate files in the build directory
+# containing the (normalized) actual output of the compiler. This
+# script will then copy that output and replace the "expected output"
+# files. You can then commit the changes.
+#
+# If you find yourself manually editing a foo.stderr file, you're
+# doing it wrong.
+
+if [[ "$1" == "--help" || "$1" == "-h" || "$1" == "" || "$2" == "" ]]; then
+ echo "usage: $0 <build-directory> <relative-path-to-rs-files>"
+ echo ""
+ echo "For example:"
+ echo " $0 ../../../build/x86_64-apple-darwin/test/ui *.rs */*.rs"
+fi
+
+MYDIR=$(dirname $0)
+
+BUILD_DIR="$1"
+shift
+
+shopt -s nullglob
+
+while [[ "$1" != "" ]]; do
+ for EXT in "stderr" "fixed"; do
+ for OUT_NAME in $BUILD_DIR/${1%.rs}.*$EXT; do
+ OUT_DIR=`dirname "$1"`
+ OUT_BASE=`basename "$OUT_NAME"`
+ if ! (diff $OUT_NAME $MYDIR/$OUT_DIR/$OUT_BASE >& /dev/null); then
+ echo updating $MYDIR/$OUT_DIR/$OUT_BASE
+ cp $OUT_NAME $MYDIR/$OUT_DIR
+ fi
+ done
+ done
+ shift
+done