Rocket

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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:
Mcontrib/codegen/Cargo.toml | 5+++++
Mcontrib/codegen/src/database.rs | 49++++++++++++++++++++++++++-----------------------
Acontrib/codegen/tests/compile-test.rs | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontrib/codegen/tests/ui-fail/database-syntax.rs | 41+++++++++++++++++++++++++++++++++++++++++
Acontrib/codegen/tests/ui-fail/database-syntax.stderr | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontrib/codegen/tests/ui-fail/database-types.rs | 10++++++++++
Acontrib/codegen/tests/ui-fail/database-types.stderr | 9+++++++++
Acontrib/codegen/tests/ui-fail/update-references.sh | 48++++++++++++++++++++++++++++++++++++++++++++++++
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