<commit_before>//! A document store with a flexible query language and built-in undo support.

use regex::Regex;
use rusqlite::{params, Connection};
use std::path::Path;
use std::result::Result as Result_;
use std::sync::Arc;
use thiserror::Error;

use crate::object::*;
use crate::query::QueryNode;

pub type Result<T, E = StoreError> = Result_<T, E>;

#[derive(Error, Debug)]
pub enum StoreError {
    #[error("database error")]
    Sqlite(#[from] rusqlite::Error),

    #[error("could not de/serialize object")]
    Serialization(#[from] serde_json::Error),
}

trait AsStoreResult<T> {
    fn as_store_result(self) -> Result<T>;
}

impl<T, E> AsStoreResult<T> for Result_<T, E>
where
    E: Into<StoreError>,
{
    fn as_store_result(self) -> Result<T> {
        self.map_err(|e| e.into())
    }
}

pub struct Store {
    conn: Connection,
}

impl Store {
    pub fn open(path: impl AsRef<Path>) -> Result<Store> {
        let mut store = Store {
            conn: Connection::open(path)?,
        };

        // Make SQLite use a write-ahead instead of a delete-based journal; see
        // [the SQLite documentation](https://www.sqlite.org/wal.html) for more info.
        store.conn.pragma_update(None, "journal_mode", &"WAL")?;

        // Check that the JSON1 extension is working.
        store
            .conn
            .prepare("SELECT json(\"{}\")")?
            .query(params![])
            // Explicitly ignore the value.
            .map(|_| {})?;

        store.upgrade_if_needed()?;

        store.add_regexp_function()?;

        Ok(store)
    }

    fn upgrade_if_needed(&mut self) -> Result<()> {
        // We check the version of the database and upgrade it if necessary.
        // Conveniently, this starts at 0 in an empty database.
        let version = self
            .conn
            .prepare("SELECT user_version from pragma_user_version")?
            .query_row(params![], |row| row.get::<usize, i64>(0))? as usize;

        // We use `AUTOINCREMENT` on the objects table so that IDs are not reused.
        let updates = [
            "
                CREATE TABLE objects (
                    object_id INTEGER PRIMARY KEY AUTOINCREMENT,
                    properties TEXT
                );
            ",
            "
                CREATE TABLE object_changes (
                    serial INTEGER PRIMARY KEY,
                    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    object_id INTEGER,
                    action TEXT,
                    previous TEXT
                );
                CREATE TABLE checkpoints (
                    checkpoint_id INTEGER PRIMARY KEY,
                    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    serial INTEGER
                );
            ",
        ];

        // We set the `user_version` after each update to ensure updates are not applied twice if one
        // in a sequence of updates fails.
        for (version, update) in updates.iter().enumerate().skip(version) {
            self.conn.execute_batch(update)?;
            self.conn
                .pragma_update(None, "user_version", &((version + 1) as i64))?;
        }

        Ok(())
    }

    fn add_regexp_function(&mut self) -> Result<()> {
        // Lifted from https://docs.rs/rusqlite/0.24.1/rusqlite/functions/index.html
        Ok(self.conn.create_scalar_function(
            "regexp",
            2,
            rusqlite::functions::FunctionFlags::SQLITE_UTF8
                | rusqlite::functions::FunctionFlags::SQLITE_DETERMINISTIC,
            move |ctx| {
                assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
                let regexp: Arc<Regex> = ctx.get_or_create_aux(
                    0,
                    |vr| -> Result<_, Box<dyn std::error::Error + Send + Sync + 'static>> {
                        Ok(Regex::new(vr.as_str()?)?)
                    },
                )?;
                let is_match = {
                    let text = ctx
                        .get_raw(1)
                        .as_str()
                        .map_err(|e| rusqlite::Error::UserFunctionError(e.into()))?;

                    regexp.is_match(text)
                };

                Ok(is_match)
            },
        )?)
    }

    pub fn all(&self) -> Collection {
        Collection {
            conn: &self.conn,
            query: QueryNode::Empty,
        }
    }

    pub fn query(&self, query: impl Into<QueryNode>) -> Collection {
        Collection {
            conn: &self.conn,
            query: query.into(),
        }
    }

    pub fn add(&mut self, object: Object) -> Result<i64> {
        let object_serialized = serde_json::to_string(&object)?;

        self.conn
            .prepare("INSERT INTO objects(properties) VALUES(?)")?
            .execute(params![object_serialized])?;

        Ok(self.conn.last_insert_rowid() as i64)
    }
}

pub struct Collection<'a> {
    conn: &'a Connection,
    query: QueryNode,
}

impl<'a> Collection<'a> {
    fn prepare_with_query(
        &self,
        prefix: &str,
    ) -> Result<(rusqlite::Statement, Vec<Box<dyn rusqlite::ToSql>>)> {
        let (where_clause, params) = self.query.to_sql_clause();
        Ok((
            self.conn
                .prepare(&format!("{} WHERE {}", prefix, where_clause))?,
            params,
        ))
    }

    pub fn len(&self) -> Result<usize> {
        let (mut statement, params) = self.prepare_with_query("SELECT COUNT(*) FROM objects")?;
        Ok(statement
            // This workaround can be removed when https://github.com/rusqlite/rusqlite/issues/700
            // is closed
            .query_row(params, |row| row.get::<usize, i64>(0))? as usize)
    }

    pub fn iter(&self) -> Result<impl Iterator<Item = Object> + 'a> {
        let (mut statement, params) =
            self.prepare_with_query("SELECT object_id, properties FROM objects")?;

        let rows = statement.query_and_then(params, |row| {
            Ok((row.get::<usize, i64>(0)?, row.get::<usize, String>(1)?))
        })?;

        let result = Ok(rows
            .map(|r: rusqlite::Result<(i64, String)>| {
                r.as_store_result()
                    .and_then(|(object_id, serialized_object)| {
                        let mut object =
                            serde_json::from_str::<Object>(&serialized_object).as_store_result()?;

                        object.insert("object-id".to_string(), PropValue::Number(object_id));
                        Ok(object)
                    })
            })
            .collect::<Result<Vec<Object>>>()?
            .into_iter());

        result
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Q;
    use tempfile::{Builder, TempDir};

    fn open_store(tempdir: &TempDir, name: &str) -> Store {
        Store::open(tempdir.path().join(name)).unwrap()
    }

    fn test_dir() -> TempDir {
        Builder::new().prefix("qualia-store").tempdir().unwrap()
    }

    fn populated_store() -> Result<(Store, TempDir)> {
        let test_dir = test_dir();
        let mut store = open_store(&test_dir, "store.qualia");

        store.add(object!("name" => "one", "blah" => "blah"))?;
        store.add(object!("name" => "two", "blah" => "halb"))?;
        store.add(object!("name" => "three", "blah" => "BLAH"))?;
        store.add(object!("name" => "four", "blah" => "blahblah"))?;

        Ok((store, test_dir))
    }

    fn sort_objects(objects: &mut Vec<Object>) {
        objects.sort_by_key(|o| {
            o.get("name")
                .expect("test objects should have a name")
                .as_str()
                .expect("`name` of test objects should be a string")
                .clone()
        })
    }

    #[test]
    fn new_store_is_empty() -> Result<()> {
        assert_eq!(open_store(&test_dir(), "store.qualia").all().len()?, 0);

        Ok(())
    }

    #[test]
    fn added_objects_exist() -> Result<()> {
        let (store, _test_dir) = populated_store()?;

        let all = store.all();

        assert_eq!(all.len()?, 4);
        let mut all_objects = all.iter()?.collect::<Vec<Object>>();
        sort_objects(&mut all_objects);
        assert_eq!(
            all_objects,
            vec![
                object!("name" => "four", "blah" => "blahblah", "object-id" => 4),
                object!("name" => "one", "blah" => "blah", "object-id" => 1),
                object!("name" => "three", "blah" => "BLAH", "object-id" => 3),
                object!("name" => "two", "blah" => "halb", "object-id" => 2),
            ],
        );

        Ok(())
    }

    #[test]
    fn added_objects_can_be_found() -> Result<()> {
        let (store, _test_dir) = populated_store()?;

        let found = store.query(QueryNode::PropEqual {
            name: "name".to_string(),
            value: PropValue::String("one".to_string()),
        });

        assert_eq!(found.len()?, 1);
        let mut found_objects = found.iter()?.collect::<Vec<Object>>();
        sort_objects(&mut found_objects);
        assert_eq!(
            found_objects,
            vec![object!("name" => "one", "blah" => "blah", "object-id" => 1)],
        );

        Ok(())
    }

    #[test]
    fn objects_can_be_found_by_their_object_id() -> Result<()> {
        let (mut store, _test_dir) = populated_store()?;

        let object_id = store.add(object!("name" => "b", "c" => "d"))?;

        let found = store.query(Q.id(object_id));

        assert_eq!(found.len()?, 1);
        let mut found_objects = found.iter()?.collect::<Vec<Object>>();
        sort_objects(&mut found_objects);
        assert_eq!(
            found_objects,
            vec![object!("name" => "b", "c" => "d", "object-id" => object_id)],
        );

        Ok(())
    }

    #[test]
    fn objects_can_be_found_by_like() -> Result<()> {
        let (store, _test_dir) = populated_store()?;

        let found = store.query(Q.like("blah", "blah"));

        assert_eq!(found.len()?, 2);
        let mut found_objects = found.iter()?.collect::<Vec<Object>>();
        sort_objects(&mut found_objects);
        assert_eq!(
            found_objects,
            vec![
                object!("name" => "one", "blah" => "blah", "object-id" => 1),
                object!("name" => "three", "blah" => "BLAH", "object-id" => 3)
            ],
        );

        Ok(())
    }

    #[test]
    fn objects_can_be_found_by_and() -> Result<()> {
        let (store, _test_dir) = populated_store()?;

        let found = store.query(Q.equal("name", "one").equal("blah", "blah"));

        assert_eq!(found.len()?, 1);
        let mut found_objects = found.iter()?.collect::<Vec<Object>>();
        sort_objects(&mut found_objects);
        assert_eq!(
            found_objects,
            vec![object!("name" => "one", "blah" => "blah", "object-id" => 1),],
        );

        Ok(())
    }
}
<commit_msg>Allow undo support<commit_after>//! A document store with a flexible query language and built-in undo support.

use regex::Regex;
use rusqlite::{params, Connection};
use std::path::Path;
use std::result::Result as Result_;
use std::sync::Arc;
use thiserror::Error;

use crate::object::*;
use crate::query::QueryNode;

pub type Result<T, E = StoreError> = Result_<T, E>;

#[derive(Error, Debug)]
pub enum StoreError {
    #[error("could not de/serialize object")]
    Serialization(#[from] serde_json::Error),

    #[error("database error")]
    Sqlite(#[from] rusqlite::Error),

    #[error("invalid usage: {0}")]
    Usage(String),
}

trait AsStoreResult<T> {
    fn as_store_result(self) -> Result<T>;
}

impl<T, E> AsStoreResult<T> for Result_<T, E>
where
    E: Into<StoreError>,
{
    fn as_store_result(self) -> Result<T> {
        self.map_err(|e| e.into())
    }
}

#[derive(Debug)]
enum ChangeType {
    Add,
    Delete,
}

impl rusqlite::ToSql for ChangeType {
    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
        match self {
            ChangeType::Add => "add",
            ChangeType::Delete => "delete",
        }
        .to_sql()
    }
}

impl rusqlite::types::FromSql for ChangeType {
    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
        match value.as_str()? {
            "add" => Ok(ChangeType::Add),
            "delete" => Ok(ChangeType::Delete),
            _ => Err(rusqlite::types::FromSqlError::InvalidType),
        }
    }
}

pub struct Store {
    conn: Connection,
}

impl Store {
    pub fn open(path: impl AsRef<Path>) -> Result<Store> {
        let mut store = Store {
            conn: Connection::open(path)?,
        };

        // Make SQLite use a write-ahead instead of a delete-based journal; see
        // [the SQLite documentation](https://www.sqlite.org/wal.html) for more info.
        store.conn.pragma_update(None, "journal_mode", &"WAL")?;

        // Check that the JSON1 extension is working.
        store
            .conn
            .prepare("SELECT json(\"{}\")")?
            .query(params![])
            // Explicitly ignore the value.
            .map(|_| {})?;

        store.upgrade_if_needed()?;

        store.add_regexp_function()?;

        Ok(store)
    }

    fn upgrade_if_needed(&mut self) -> Result<()> {
        // We check the version of the database and upgrade it if necessary.
        // Conveniently, this starts at 0 in an empty database.
        let version = self
            .conn
            .prepare("SELECT user_version from pragma_user_version")?
            .query_row(params![], |row| row.get::<usize, i64>(0))? as usize;

        // We use `AUTOINCREMENT` on the objects table so that IDs are not reused.
        let updates = [
            "
                CREATE TABLE objects (
                    object_id INTEGER PRIMARY KEY AUTOINCREMENT,
                    properties TEXT
                );
            ",
            "
                CREATE TABLE object_changes (
                    serial INTEGER PRIMARY KEY,
                    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    object_id INTEGER,
                    action TEXT,
                    previous TEXT
                );
                CREATE TABLE checkpoints (
                    checkpoint_id INTEGER PRIMARY KEY,
                    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    serial INTEGER
                );
            ",
            "
                ALTER TABLE checkpoints ADD description TEXT
            ",
        ];

        // We set the `user_version` after each update to ensure updates are not applied twice if one
        // in a sequence of updates fails.
        for (version, update) in updates.iter().enumerate().skip(version) {
            self.conn.execute_batch(update)?;
            self.conn
                .pragma_update(None, "user_version", &((version + 1) as i64))?;
        }

        Ok(())
    }

    fn add_regexp_function(&mut self) -> Result<()> {
        // Lifted from https://docs.rs/rusqlite/0.24.1/rusqlite/functions/index.html
        Ok(self.conn.create_scalar_function(
            "regexp",
            2,
            rusqlite::functions::FunctionFlags::SQLITE_UTF8
                | rusqlite::functions::FunctionFlags::SQLITE_DETERMINISTIC,
            move |ctx| {
                assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
                let regexp: Arc<Regex> = ctx.get_or_create_aux(
                    0,
                    |vr| -> Result<_, Box<dyn std::error::Error + Send + Sync + 'static>> {
                        Ok(Regex::new(vr.as_str()?)?)
                    },
                )?;
                let is_match = {
                    let text = ctx
                        .get_raw(1)
                        .as_str()
                        .map_err(|e| rusqlite::Error::UserFunctionError(e.into()))?;

                    regexp.is_match(text)
                };

                Ok(is_match)
            },
        )?)
    }

    pub fn all(&self) -> Collection {
        Collection {
            conn: &self.conn,
            query: QueryNode::Empty,
        }
    }

    pub fn query(&self, query: impl Into<QueryNode>) -> Collection {
        Collection {
            conn: &self.conn,
            query: query.into(),
        }
    }

    pub fn checkpoint(&mut self) -> Result<Checkpoint<'_>> {
        Checkpoint::new(self)
    }

    pub fn undo(&mut self) -> Result<()> {
        let transaction = self.conn.transaction()?;

        let (cur_checkpoint_serial, prev_checkpoint_serial) = {
            let last_two_checkpoint_serials: Vec<i64> = transaction
                .prepare(
                    "SELECT serial
                        FROM checkpoints
                        ORDER BY serial DESC
                        LIMIT 2
                    ",
                )?
                .query_and_then(params![], |row| row.get(0).as_store_result())?
                .collect::<Result<_>>()?;

            if last_two_checkpoint_serials.len() == 0 {
                return Ok(());
            }

            (
                last_two_checkpoint_serials[0],
                *last_two_checkpoint_serials.get(1).unwrap_or(&0),
            )
        };

        let changes = transaction
            .prepare(
                "SELECT
                    action, object_id, previous
                    FROM object_changes
                    WHERE serial > ?
                    ORDER BY serial DESC
            ",
            )?
            .query_and_then(
                params![prev_checkpoint_serial],
                |row| -> Result<(ChangeType, i64, String)> {
                    Ok((row.get(0)?, row.get(1)?, (row.get(2)?)))
                },
            )?
            .collect::<Result<Vec<_>>>()
            .as_store_result()?;

        dbg!(&changes);

        for (change_type, object_id, previous_serialized) in changes {
            match change_type {
                ChangeType::Add => assert_eq!(
                    transaction.execute(
                        "DELETE
                            FROM objects
                            WHERE object_id = ?",
                        params![object_id]
                    )?,
                    1
                ),
                ChangeType::Delete => assert_eq!(
                    transaction.execute(
                        "INSERT
                            INTO objects(object_id, properties)
                            VALUES(?, ?)
                        ",
                        params![object_id, previous_serialized]
                    )?,
                    1
                ),
            }
        }

        transaction.execute(
            "DELETE
                FROM object_changes
                WHERE serial > ?
            ",
            params![prev_checkpoint_serial],
        )?;

        transaction.execute(
            "DELETE
                FROM checkpoints
                WHERE serial = ?
            ",
            params![cur_checkpoint_serial],
        )?;

        transaction.commit()?;

        Ok(())
    }
}

pub struct Checkpoint<'a> {
    store: &'a Store,
    transaction: rusqlite::Transaction<'a>,
}

impl<'a> Checkpoint<'a> {
    fn new(store: &'a mut Store) -> Result<Checkpoint> {
        let transaction = store.conn.unchecked_transaction()?;

        Ok(Checkpoint { store, transaction })
    }

    fn create_checkpoint(&self) -> Result<()> {
        self.transaction.execute(
            "INSERT
                INTO checkpoints(serial)
                VALUES(
                    (SELECT
                        IFNULL(MAX(serial), 0)
                        FROM object_changes
                    )
                )
            ",
            params![],
        )?;

        Ok(())
    }

    pub fn commit(self) -> Result<()> {
        self.create_checkpoint()?;
        self.transaction.commit().as_store_result()
    }

    fn record_change(
        &self,
        change_type: ChangeType,
        object_id: i64,
        previous: impl AsRef<str>,
    ) -> Result<()> {
        self.transaction.execute(
            "INSERT
                INTO object_changes(action, object_id, previous)
                VALUES(?, ?, ?)
            ",
            params![change_type, object_id, previous.as_ref()],
        )?;

        Ok(())
    }

    pub fn add(&self, object: Object) -> Result<i64> {
        let object_serialized = serde_json::to_string(&object)?;

        self.transaction
            .prepare("INSERT INTO objects(properties) VALUES(?)")?
            .execute(params![object_serialized])?;

        let object_id = self.store.conn.last_insert_rowid();
        self.record_change(ChangeType::Add, object_id, "{}")?;

        Ok(object_id as i64)
    }

    pub fn query(&self, query: impl Into<QueryNode>) -> MutableCollection {
        MutableCollection {
            checkpoint: &self,
            collection: Collection {
                conn: &self.transaction,
                query: query.into(),
            },
        }
    }
}

pub struct Collection<'a> {
    conn: &'a Connection,
    query: QueryNode,
}

impl<'a> Collection<'a> {
    fn prepare_with_query(
        &self,
        prefix: &str,
    ) -> Result<(rusqlite::Statement, Vec<Box<dyn rusqlite::ToSql>>)> {
        let (where_clause, params) = self.query.to_sql_clause();
        Ok((
            self.conn
                .prepare(&format!("{} WHERE {}", prefix, where_clause))?,
            params,
        ))
    }

    pub fn len(&self) -> Result<usize> {
        let (mut statement, params) = self.prepare_with_query("SELECT COUNT(*) FROM objects")?;
        Ok(statement
            // This workaround can be removed when https://github.com/rusqlite/rusqlite/issues/700
            // is closed
            .query_row(params, |row| row.get::<usize, i64>(0))? as usize)
    }

    pub fn exists(&self) -> Result<bool> {
        Ok(self.len()? != 0)
    }

    pub fn iter(&self) -> Result<impl Iterator<Item = Object> + 'a> {
        let (mut statement, params) =
            self.prepare_with_query("SELECT object_id, properties FROM objects")?;

        let rows = statement.query_and_then(params, |row| {
            Ok((row.get::<usize, i64>(0)?, row.get::<usize, String>(1)?))
        })?;

        let result = Ok(rows
            .map(|r: rusqlite::Result<(i64, String)>| {
                r.as_store_result()
                    .and_then(|(object_id, serialized_object)| {
                        let mut object =
                            serde_json::from_str::<Object>(&serialized_object).as_store_result()?;

                        object.insert("object-id".to_string(), PropValue::Number(object_id));
                        Ok(object)
                    })
            })
            .collect::<Result<Vec<Object>>>()?
            .into_iter());

        result
    }
}

pub struct MutableCollection<'a> {
    checkpoint: &'a Checkpoint<'a>,
    collection: Collection<'a>,
}

impl<'a> MutableCollection<'a> {
    pub fn delete(&self) -> Result<usize> {
        for object in self.iter()? {
            self.checkpoint.record_change(
                ChangeType::Delete,
                object["object-id"].as_number().unwrap(),
                serde_json::to_string(&object)?,
            )?;
        }

        let (mut statement, params) = self.prepare_with_query("DELETE FROM objects")?;
        statement.execute(params).as_store_result()
    }
}

impl<'a> std::ops::Deref for MutableCollection<'a> {
    type Target = Collection<'a>;

    fn deref(&self) -> &Collection<'a> {
        &self.collection
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Q;
    use tempfile::{Builder, TempDir};

    fn open_store<'a>(tempdir: &TempDir, name: &str) -> Store {
        Store::open(tempdir.path().join(name)).unwrap()
    }

    fn test_dir() -> TempDir {
        Builder::new().prefix("qualia-store").tempdir().unwrap()
    }

    fn populated_store() -> Result<(Store, TempDir)> {
        let test_dir = test_dir();
        let mut store = open_store(&test_dir, "store.qualia");

        let checkpoint = store.checkpoint()?;
        checkpoint.add(object!("name" => "one", "blah" => "blah"))?;
        checkpoint.add(object!("name" => "two", "blah" => "halb"))?;
        checkpoint.add(object!("name" => "three", "blah" => "BLAH"))?;
        checkpoint.add(object!("name" => "four", "blah" => "blahblah"))?;
        checkpoint.commit()?;

        Ok((store, test_dir))
    }

    fn sort_objects(objects: &mut Vec<Object>) {
        objects.sort_by_key(|o| {
            o.get("name")
                .expect("test objects should have a name")
                .as_str()
                .expect("`name` of test objects should be a string")
                .clone()
        })
    }

    #[test]
    fn new_store_is_empty() -> Result<()> {
        assert_eq!(open_store(&test_dir(), "store.qualia").all().len()?, 0);

        Ok(())
    }

    #[test]
    fn added_objects_exist() -> Result<()> {
        let (store, _test_dir) = populated_store()?;

        let all = store.all();

        assert_eq!(all.len()?, 4);
        let mut all_objects = all.iter()?.collect::<Vec<Object>>();
        sort_objects(&mut all_objects);
        assert_eq!(
            all_objects,
            vec![
                object!("name" => "four", "blah" => "blahblah", "object-id" => 4),
                object!("name" => "one", "blah" => "blah", "object-id" => 1),
                object!("name" => "three", "blah" => "BLAH", "object-id" => 3),
                object!("name" => "two", "blah" => "halb", "object-id" => 2),
            ],
        );

        Ok(())
    }

    #[test]
    fn adding_objects_can_be_undone() -> Result<()> {
        let (mut store, _test_dir) = populated_store()?;

        let checkpoint = store.checkpoint()?;
        let object_id = checkpoint.add(object!("name" => "b", "c" => "d"))?;
        checkpoint.commit()?;

        assert!(store.query(Q.id(object_id)).exists()?);
        assert_eq!(store.all().len()?, 5);

        store.undo()?;

        assert!(!store.query(Q.id(object_id)).exists()?);
        assert_eq!(store.all().len()?, 4);

        Ok(())
    }

    #[test]
    fn added_objects_can_be_found() -> Result<()> {
        let (store, _test_dir) = populated_store()?;

        let found = store.query(QueryNode::PropEqual {
            name: "name".to_string(),
            value: PropValue::String("one".to_string()),
        });

        assert_eq!(found.len()?, 1);
        let mut found_objects = found.iter()?.collect::<Vec<Object>>();
        sort_objects(&mut found_objects);
        assert_eq!(
            found_objects,
            vec![object!("name" => "one", "blah" => "blah", "object-id" => 1)],
        );

        Ok(())
    }

    #[test]
    fn objects_can_be_deleted() -> Result<()> {
        let (mut store, _test_dir) = populated_store()?;

        let checkpoint = store.checkpoint()?;
        let object_id = checkpoint.add(object!("name" => "b", "c" => "d"))?;
        checkpoint.commit()?;

        let checkpoint = store.checkpoint()?;
        checkpoint.query(Q.id(object_id)).delete()?;
        checkpoint.commit()?;

        assert!(!store.query(Q.id(object_id)).exists()?);
        assert_eq!(store.all().len()?, 4);

        Ok(())
    }

    #[test]
    fn deleting_objects_can_be_undone() -> Result<()> {
        let (mut store, _test_dir) = populated_store()?;

        let checkpoint = store.checkpoint()?;
        assert_eq!(checkpoint.query(Q.equal("name", "one")).delete()?, 1);
        checkpoint.commit()?;

        store.undo()?;

        assert!(store.query(Q.equal("name", "one")).exists()?);
        assert_eq!(store.all().len()?, 4);

        Ok(())
    }

    #[test]
    fn can_undo_to_an_empty_store() -> Result<()> {
        let (mut store, _test_dir) = populated_store()?;

        store.undo()?;
        assert_eq!(store.all().len()?, 0);

        store.undo()?;

        Ok(())
    }

    #[test]
    fn objects_can_be_found_by_their_object_id() -> Result<()> {
        let (mut store, _test_dir) = populated_store()?;

        let checkpoint = store.checkpoint()?;
        let object_id = checkpoint.add(object!("name" => "b", "c" => "d"))?;
        checkpoint.commit()?;

        let found = store.query(Q.id(object_id));

        assert_eq!(found.len()?, 1);
        let mut found_objects = found.iter()?.collect::<Vec<Object>>();
        sort_objects(&mut found_objects);
        assert_eq!(
            found_objects,
            vec![object!("name" => "b", "c" => "d", "object-id" => object_id)],
        );

        Ok(())
    }

    #[test]
    fn objects_can_be_found_by_like() -> Result<()> {
        let (store, _test_dir) = populated_store()?;

        let found = store.query(Q.like("blah", "blah"));

        assert_eq!(found.len()?, 2);
        let mut found_objects = found.iter()?.collect::<Vec<Object>>();
        sort_objects(&mut found_objects);
        assert_eq!(
            found_objects,
            vec![
                object!("name" => "one", "blah" => "blah", "object-id" => 1),
                object!("name" => "three", "blah" => "BLAH", "object-id" => 3)
            ],
        );

        Ok(())
    }

    #[test]
    fn objects_can_be_found_by_and() -> Result<()> {
        let (store, _test_dir) = populated_store()?;

        let found = store.query(Q.equal("name", "one").equal("blah", "blah"));

        assert_eq!(found.len()?, 1);
        let mut found_objects = found.iter()?.collect::<Vec<Object>>();
        sort_objects(&mut found_objects);
        assert_eq!(
            found_objects,
            vec![object!("name" => "one", "blah" => "blah", "object-id" => 1),],
        );

        Ok(())
    }
}
<|endoftext|>


<commit_before>use super::{start};

use std;
use std::io::{Read, Write};
use std::sync::{Arc, Mutex};

use time::{SteadyTime, Duration};

use mio;

#[test]
fn empty_handler() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |_| {
        let mut lock = finished_copy.lock().unwrap();
        *lock = true;

        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn empty_subcoroutines() {
    let counter = Arc::new(Mutex::new(0i32));

    let counter_copy = counter.clone();

    start(move |mioco| {

        for _ in 0..512 {
            let counter_subcopy = counter_copy.clone();
            mioco.spawn(move |_| {
                let mut lock = counter_subcopy.lock().unwrap();
                *lock += 1;

                Ok(())
            });
        }

        let mut lock = counter_copy.lock().unwrap();
        *lock += 1;

        Ok(())
    });

    assert_eq!(*counter.lock().unwrap(), 512 + 1);
}

#[test]
fn contain_panics() {
    let finished_ok = Arc::new(Mutex::new(false));

    start(move |_| {
        panic!()
    });

    assert!(!*finished_ok.lock().unwrap());
}

#[test]
fn contain_panics_in_subcoroutines() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        for _ in 0..512 {
            mioco.spawn(|_| {
                panic!()
            });
        }

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;

        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn long_chain() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        let (first_reader, first_writer) = try!(mio::unix::pipe());

        let mut prev_reader = first_reader;

        // TODO: increase after https://github.com/dpc/mioco/issues/8 is fixed
        for _ in 0..128 {
            let (reader, writer) = try!(mio::unix::pipe());

            mioco.spawn(move |mioco| {
                let mut reader = mioco.wrap(prev_reader);
                let mut writer = mioco.wrap(writer);

                let _ = std::io::copy(&mut reader, &mut writer);

                Ok(())
            });

            prev_reader = reader;
        }

        let mut first_writer = mioco.wrap(first_writer);
        let mut last_reader = mioco.wrap(prev_reader);

        for i in 0..9 {
            let test_str = format!("TeSt{}", i);
            let _ = first_writer.write_all(test_str.as_str().as_bytes());
            let mut buf = [0u8; 16];

            let _ = last_reader.read(&mut buf);

            if &buf[0..5] != test_str.as_str().as_bytes() {
                panic!();
            }
        }

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;

        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn lots_of_event_sources() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        let (first_reader, first_writer) = try!(mio::unix::pipe());

        let mut prev_reader = first_reader;

        // TODO: increase after https://github.com/dpc/mioco/issues/8 is fixed
        for _ in 0..4 {
            let (reader, writer) = try!(mio::unix::pipe());

            mioco.spawn(move |mioco| {
                // This fake readers are not really used, they are just registered for the sake of
                // testing if event sources registered with high id number are handled correctly
                let mut readers = Vec::new();
                let mut writers = Vec::new();
                for _ in 0..100 {
                    let (r, w) = try!(mio::unix::pipe());
                    readers.push(mioco.wrap(r));
                    writers.push(mioco.wrap(w));
                }

                let mut reader = mioco.wrap(prev_reader);
                let mut writer = mioco.wrap(writer);

                let _ = std::io::copy(&mut reader, &mut writer);

                Ok(())
            });

            prev_reader = reader;
        }

        let mut first_writer = mioco.wrap(first_writer);
        let mut last_reader = mioco.wrap(prev_reader);

        for i in 0..9 {
            let test_str = format!("TeSt{}", i);
            let _ = first_writer.write_all(test_str.as_str().as_bytes());
            let mut buf = [0u8; 16];

            let _ = last_reader.read(&mut buf);

            if &buf[0..5] != test_str.as_str().as_bytes() {
                panic!();
            }
        }

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;

        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn timer_times_out() {
    let finished_ok_1 = Arc::new(Mutex::new(false));
    let finished_ok_2 = Arc::new(Mutex::new(false));

    let finished_ok_1_copy = finished_ok_1.clone();
    let finished_ok_2_copy = finished_ok_2.clone();
    start(move |mioco| {

        let (reader, writer) = try!(mio::unix::pipe());

        mioco.spawn(move |mioco| {
            let reader = mioco.wrap(reader);
            let timer_id = mioco.timer().id();
            mioco.timer().set_timeout(50);
            let ev = mioco.select_read_from(&[reader.id(), timer_id]);
            assert_eq!(ev.id(), timer_id);

            let mut lock = finished_ok_1_copy.lock().unwrap();
            *lock = true;
            Ok(())
        });

        mioco.spawn(move |mioco| {
            let mut writer = mioco.wrap(writer);
            let timer_id = mioco.timer().id();
            mioco.sleep(100);
            let _ = mioco.select_read_from(&[timer_id]);
            let _ = writer.write_all("test".as_bytes());

            let mut lock = finished_ok_2_copy.lock().unwrap();
            *lock = true;
            Ok(())
        });

        Ok(())
    });

    assert!(*finished_ok_1.lock().unwrap());
    assert!(*finished_ok_2.lock().unwrap());
}

#[test]
fn timer_default_timeout() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_ok_copy = finished_ok.clone();
    start(move |mioco| {

        mioco.spawn(move |mioco| {
            let timer_id = mioco.timer().id();
            let ev = mioco.select_read_from(&[timer_id]);
            assert_eq!(ev.id(), timer_id);

            let mut lock = finished_ok_copy.lock().unwrap();
            *lock = true;
            Ok(())
        });

        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn sleep_takes_time() {
    let starting_time = SteadyTime::now();

    start(move |mioco| {
        mioco.sleep(500); Ok(())
    });

    assert!((SteadyTime::now() - starting_time) >= Duration::milliseconds(500));
}

#[test]
fn timer_select_takes_time() {
    let starting_time = SteadyTime::now();

    start(move |mioco| {
        let timer_id = mioco.timer().id();
        mioco.timer().set_timeout(500);
        let ev = mioco.select_read_from(&[timer_id]);
        assert_eq!(mioco.timer().id(), ev.id());
        Ok(())
    });

    assert!((SteadyTime::now() - starting_time) >= Duration::milliseconds(500));
}

#[test]
fn exit_notifier_simple() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        let notify = mioco.spawn(move |_| {
            Ok(())
        }).exit_notificator();

        let mut notify = mioco.wrap(notify);

        assert!(!notify.recv().unwrap().is_panic());

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;
        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn exit_notifier_simple_panic() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        let notify = mioco.spawn(move |_| {
            panic!()
        }).exit_notificator();

        let mut notify = mioco.wrap(notify);

        assert!(notify.recv().unwrap().is_panic());

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;
        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn exit_notifier_wrap_after_finish() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        let handle1 = mioco.spawn(move |_| {
            panic!()
        });

        let notify1 = handle1.exit_notificator();

        let handle2 = mioco.spawn(move |mioco| {
            let mut notify1 = mioco.wrap(notify1);
            assert!(notify1.recv().unwrap().is_panic());
            Ok(())
        });

        let notify2 = handle2.exit_notificator();
        let mut notify2 = mioco.wrap(notify2);
        assert!(!notify2.recv().unwrap().is_panic());

        let notify1 = handle1.exit_notificator();
        let mut notify1 = mioco.wrap(notify1);
        assert!(notify1.recv().unwrap().is_panic());

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;
        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}
<commit_msg>Add basic timer stress test.<commit_after>use super::{start};

use std;
use std::io::{Read, Write};
use std::sync::{Arc, Mutex};

use time::{SteadyTime, Duration};

use mio;

#[test]
fn empty_handler() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |_| {
        let mut lock = finished_copy.lock().unwrap();
        *lock = true;

        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn empty_subcoroutines() {
    let counter = Arc::new(Mutex::new(0i32));

    let counter_copy = counter.clone();

    start(move |mioco| {

        for _ in 0..512 {
            let counter_subcopy = counter_copy.clone();
            mioco.spawn(move |_| {
                let mut lock = counter_subcopy.lock().unwrap();
                *lock += 1;

                Ok(())
            });
        }

        let mut lock = counter_copy.lock().unwrap();
        *lock += 1;

        Ok(())
    });

    assert_eq!(*counter.lock().unwrap(), 512 + 1);
}

#[test]
fn contain_panics() {
    let finished_ok = Arc::new(Mutex::new(false));

    start(move |_| {
        panic!()
    });

    assert!(!*finished_ok.lock().unwrap());
}

#[test]
fn contain_panics_in_subcoroutines() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        for _ in 0..512 {
            mioco.spawn(|_| {
                panic!()
            });
        }

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;

        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn long_chain() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        let (first_reader, first_writer) = try!(mio::unix::pipe());

        let mut prev_reader = first_reader;

        // TODO: increase after https://github.com/dpc/mioco/issues/8 is fixed
        for _ in 0..128 {
            let (reader, writer) = try!(mio::unix::pipe());

            mioco.spawn(move |mioco| {
                let mut reader = mioco.wrap(prev_reader);
                let mut writer = mioco.wrap(writer);

                let _ = std::io::copy(&mut reader, &mut writer);

                Ok(())
            });

            prev_reader = reader;
        }

        let mut first_writer = mioco.wrap(first_writer);
        let mut last_reader = mioco.wrap(prev_reader);

        for i in 0..9 {
            let test_str = format!("TeSt{}", i);
            let _ = first_writer.write_all(test_str.as_str().as_bytes());
            let mut buf = [0u8; 16];

            let _ = last_reader.read(&mut buf);

            if &buf[0..5] != test_str.as_str().as_bytes() {
                panic!();
            }
        }

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;

        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn lots_of_event_sources() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        let (first_reader, first_writer) = try!(mio::unix::pipe());

        let mut prev_reader = first_reader;

        // TODO: increase after https://github.com/dpc/mioco/issues/8 is fixed
        for _ in 0..4 {
            let (reader, writer) = try!(mio::unix::pipe());

            mioco.spawn(move |mioco| {
                // This fake readers are not really used, they are just registered for the sake of
                // testing if event sources registered with high id number are handled correctly
                let mut readers = Vec::new();
                let mut writers = Vec::new();
                for _ in 0..100 {
                    let (r, w) = try!(mio::unix::pipe());
                    readers.push(mioco.wrap(r));
                    writers.push(mioco.wrap(w));
                }

                let mut reader = mioco.wrap(prev_reader);
                let mut writer = mioco.wrap(writer);

                let _ = std::io::copy(&mut reader, &mut writer);

                Ok(())
            });

            prev_reader = reader;
        }

        let mut first_writer = mioco.wrap(first_writer);
        let mut last_reader = mioco.wrap(prev_reader);

        for i in 0..9 {
            let test_str = format!("TeSt{}", i);
            let _ = first_writer.write_all(test_str.as_str().as_bytes());
            let mut buf = [0u8; 16];

            let _ = last_reader.read(&mut buf);

            if &buf[0..5] != test_str.as_str().as_bytes() {
                panic!();
            }
        }

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;

        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn timer_times_out() {
    let finished_ok_1 = Arc::new(Mutex::new(false));
    let finished_ok_2 = Arc::new(Mutex::new(false));

    let finished_ok_1_copy = finished_ok_1.clone();
    let finished_ok_2_copy = finished_ok_2.clone();
    start(move |mioco| {

        let (reader, writer) = try!(mio::unix::pipe());

        mioco.spawn(move |mioco| {
            let reader = mioco.wrap(reader);
            let timer_id = mioco.timer().id();
            mioco.timer().set_timeout(50);
            let ev = mioco.select_read_from(&[reader.id(), timer_id]);
            assert_eq!(ev.id(), timer_id);

            let mut lock = finished_ok_1_copy.lock().unwrap();
            *lock = true;
            Ok(())
        });

        mioco.spawn(move |mioco| {
            let mut writer = mioco.wrap(writer);
            let timer_id = mioco.timer().id();
            mioco.sleep(100);
            let _ = mioco.select_read_from(&[timer_id]);
            let _ = writer.write_all("test".as_bytes());

            let mut lock = finished_ok_2_copy.lock().unwrap();
            *lock = true;
            Ok(())
        });

        Ok(())
    });

    assert!(*finished_ok_1.lock().unwrap());
    assert!(*finished_ok_2.lock().unwrap());
}

#[test]
fn timer_default_timeout() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_ok_copy = finished_ok.clone();
    start(move |mioco| {

        mioco.spawn(move |mioco| {
            let timer_id = mioco.timer().id();
            let ev = mioco.select_read_from(&[timer_id]);
            assert_eq!(ev.id(), timer_id);

            let mut lock = finished_ok_copy.lock().unwrap();
            *lock = true;
            Ok(())
        });

        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn sleep_takes_time() {
    let starting_time = SteadyTime::now();

    start(move |mioco| {
        mioco.sleep(500); Ok(())
    });

    assert!((SteadyTime::now() - starting_time) >= Duration::milliseconds(500));
}

#[test]
fn timer_select_takes_time() {
    let starting_time = SteadyTime::now();

    start(move |mioco| {
        let timer_id = mioco.timer().id();
        mioco.timer().set_timeout(500);
        let ev = mioco.select_read_from(&[timer_id]);
        assert_eq!(mioco.timer().id(), ev.id());
        Ok(())
    });

    assert!((SteadyTime::now() - starting_time) >= Duration::milliseconds(500));
}

#[test]
fn basic_timer_stress_test() {
    start(move |mioco| {
        for _ in 0..10 {
            for t in 0..100 {
                mioco.spawn(move |mioco| {
                    mioco.sleep(t);
                    Ok(())
                });

            }

            mioco.sleep(1);
        }
        Ok(())
    });
}

#[test]
fn exit_notifier_simple() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        let notify = mioco.spawn(move |_| {
            Ok(())
        }).exit_notificator();

        let mut notify = mioco.wrap(notify);

        assert!(!notify.recv().unwrap().is_panic());

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;
        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn exit_notifier_simple_panic() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        let notify = mioco.spawn(move |_| {
            panic!()
        }).exit_notificator();

        let mut notify = mioco.wrap(notify);

        assert!(notify.recv().unwrap().is_panic());

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;
        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}

#[test]
fn exit_notifier_wrap_after_finish() {
    let finished_ok = Arc::new(Mutex::new(false));

    let finished_copy = finished_ok.clone();
    start(move |mioco| {

        let handle1 = mioco.spawn(move |_| {
            panic!()
        });

        let notify1 = handle1.exit_notificator();

        let handle2 = mioco.spawn(move |mioco| {
            let mut notify1 = mioco.wrap(notify1);
            assert!(notify1.recv().unwrap().is_panic());
            Ok(())
        });

        let notify2 = handle2.exit_notificator();
        let mut notify2 = mioco.wrap(notify2);
        assert!(!notify2.recv().unwrap().is_panic());

        let notify1 = handle1.exit_notificator();
        let mut notify1 = mioco.wrap(notify1);
        assert!(notify1.recv().unwrap().is_panic());

        let mut lock = finished_copy.lock().unwrap();
        *lock = true;
        Ok(())
    });

    assert!(*finished_ok.lock().unwrap());
}
<|endoftext|>


<commit_before>//! Provides the Timer struct, which keeps track of the current playback speed and what index we are at.

use std::cmp::Ordering;

/// Keeps track of the current playback speed and what index we are at.
pub struct Timer {
    dts : Vec<f32>, // possible dt values
    dti : isize, // which of dts we're talking about. 0 is stop, 1 => dts[0], -1 => -dts[0]
    len : Option<usize>, // length of what we're iterating over
    /// Current index, as float; keeps track of partials
    pub t : f32,
    /// Time to pause before re-looping. None means "don't loop".
    pub loop_pause : Option<f32>,
    /// Maximum rate
    pub fps : f32,
}

impl Timer {
    /// Make a new timer
    pub fn new(dts : Vec<f32>, len : Option<usize>) -> Timer {
        let mut new_dts = if dts.len() == 0 {
            vec!(1f32)
        } else {
            dts
        };
        new_dts.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));

        Timer {
            dts: new_dts,
            dti: 1,
            len: len,
            t : 0.0,
            loop_pause : None,
            fps : 1.0,
        }
    }

    /// set speed to at least a given value. Direction is taken into account.
    /// Returns the new speed.
    pub fn at_least(&mut self, new_speed : f32) -> f32 {
        let signum = new_speed.signum();
        let abs_speed = new_speed * signum;
        for new_ix in 0isize..((self.dts.len()+1) as isize) {
            let speed = if new_ix == 0 {0.} else {self.dts[(new_ix-1) as usize]};
            self.dti = new_ix * (signum as isize);
            if speed >= abs_speed {
                break;
            }
        }
        return self.get_dt();
    }

    /// Switch forwards vs. backwards. If stopped, it stays stopped.
    pub fn switch_direction(&mut self) {
        self.dti = -self.dti;
    }

    /// Increment faster
    pub fn faster(&mut self) {
        self.dti = match self.dti {
            0 => 1,
            i if i >= self.dts.len() as isize => self.dts.len() as isize,
            i if i <= -(self.dts.len() as isize) => -(self.dts.len() as isize),
            i if i > 0 => i+1,
            i => i-1
        };
    }

    /// Increment slower
    pub fn slower(&mut self) {
        self.dti = match self.dti {
            0 => 0,
            i if i > 0 => i-1,
            i => i+1
        };
    }

    /// Get current dt
    pub fn get_dt(&self) -> f32{
        match self.dti {
            0 => 0.,
            i if i > 0 => self.dts[(i-1) as usize],
            i => -self.dts[(-1-i) as usize]
        }
    }

    /// Current time, as f32
    pub fn get_time(&self) -> f32 {
        self.t
    }

    /// Increment the timer, and return current index
    pub fn incr(&mut self) -> usize {
        self.t += self.get_dt() / self.fps;

        match (self.len, self.loop_pause) {
            (None, _) if self.t < 0. => {
                    self.t = 0.;
                    0
                },
            (None, _) => self.t as usize,
            (_, None) if self.t < 0. => {
                    // fixed length, but no loop, but t is negative
                    self.t = 0.;
                    0
                },
            (Some(len), None) => {
                // We have a fixed length, but we don't loop.
                let ix = self.t as usize;
                if ix >= len { len - 1 } else { ix }
            }
            (Some(len), Some(pause)) => {
                // We have a fixed length, but we don't loop.
                let loop_len = (len as f32) + pause;
                let ix = (self.t % loop_len) as usize;
                if ix >= len { len - 1 } else { ix }
            }
        }
    }
}

#[cfg(test)]
mod test {

    #[test]
    fn timer_dts() {
        let mut t = ::Timer::new(vec!(1.,2.,4.), None);
        assert_eq!(t.get_dt(), 1.);
        assert_eq!(t.incr(), 1);
        t.faster();
        assert_eq!(t.get_dt(), 2.);
        assert_eq!(t.incr(), 3);
        t.faster();
        assert_eq!(t.get_dt(), 4.);
        assert_eq!(t.incr(), 7);
        t.faster();
        assert_eq!(t.get_dt(), 4.);
        assert_eq!(t.incr(), 11);
        t.switch_direction();
        assert_eq!(t.get_dt(), -4.);
        assert_eq!(t.incr(), 7);
        t.faster();
        assert_eq!(t.get_dt(), -4.);
        assert_eq!(t.incr(), 3);
        t.switch_direction();
        assert_eq!(t.get_dt(), 4.);
        assert_eq!(t.incr(), 7);
        t.switch_direction();
        assert_eq!(t.get_dt(), -4.);
        assert_eq!(t.incr(), 3);
        t.slower();
        assert_eq!(t.get_dt(), -2.);
        assert_eq!(t.incr(), 1);
        t.slower();
        assert_eq!(t.get_dt(), -1.);
        assert_eq!(t.incr(), 0);
        t.slower();
        assert_eq!(t.get_dt(), 0.);
        assert_eq!(t.incr(), 0);
        t.slower();
        assert_eq!(t.get_dt(), 0.);
        assert_eq!(t.incr(), 0);
    }

    #[test]
    fn timer_pauseloop() {
        let mut t = ::Timer::new(vec!(1.,2.,4.), Some(5));
        t.loop_pause = Some(5.);
        t.fps = 2.;
        t.faster();
        t.faster();
        assert_eq!(t.get_dt(), 4.);
        assert_eq!(t.incr(), 2); // t = 2.
        assert_eq!(t.incr(), 4); // t = 4.
        assert_eq!(t.incr(), 4); // t = 6.
        assert_eq!(t.incr(), 4); // t = 8.
        assert_eq!(t.incr(), 0); // t = 10.
    }
}

<commit_msg>src: timer now loops<commit_after>//! Provides the Timer struct, which keeps track of the current playback speed and what index we are at.

use std::cmp::Ordering;

/// Keeps track of the current playback speed and what index we are at.
pub struct Timer {
    dts : Vec<f32>, // possible dt values
    dti : isize, // which of dts we're talking about. 0 is stop, 1 => dts[0], -1 => -dts[0]
    len : Option<usize>, // length of what we're iterating over
    /// Current index, as float; keeps track of partials
    pub t : f32,
    /// Time to pause before re-looping. None means "don't loop".
    pub loop_pause : Option<f32>,
    /// Maximum rate
    pub fps : f32,
}

impl Timer {
    /// Make a new timer
    pub fn new(dts : Vec<f32>, len : Option<usize>) -> Timer {
        let mut new_dts = if dts.len() == 0 {
            vec!(1f32)
        } else {
            dts
        };
        new_dts.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));

        Timer {
            dts: new_dts,
            dti: 1,
            len: len,
            t : 0.0,
            loop_pause : None,
            fps : 1.0,
        }
    }

    /// set speed to at least a given value. Direction is taken into account.
    /// Returns the new speed.
    pub fn at_least(&mut self, new_speed : f32) -> f32 {
        let signum = new_speed.signum();
        let abs_speed = new_speed * signum;
        for new_ix in 0isize..((self.dts.len()+1) as isize) {
            let speed = if new_ix == 0 {0.} else {self.dts[(new_ix-1) as usize]};
            self.dti = new_ix * (signum as isize);
            if speed >= abs_speed {
                break;
            }
        }
        return self.get_dt();
    }

    /// Switch forwards vs. backwards. If stopped, it stays stopped.
    pub fn switch_direction(&mut self) {
        self.dti = -self.dti;
    }

    /// Increment faster
    pub fn faster(&mut self) {
        self.dti = match self.dti {
            0 => 1,
            i if i >= self.dts.len() as isize => self.dts.len() as isize,
            i if i <= -(self.dts.len() as isize) => -(self.dts.len() as isize),
            i if i > 0 => i+1,
            i => i-1
        };
    }

    /// Increment slower
    pub fn slower(&mut self) {
        self.dti = match self.dti {
            0 => 0,
            i if i > 0 => i-1,
            i => i+1
        };
    }

    /// Get current dt
    pub fn get_dt(&self) -> f32{
        match self.dti {
            0 => 0.,
            i if i > 0 => self.dts[(i-1) as usize],
            i => -self.dts[(-1-i) as usize]
        }
    }

    /// Current time, as f32
    pub fn get_time(&self) -> f32 {
        self.t
    }

    /// Increment the timer, and return current index
    pub fn incr(&mut self) -> usize {
        self.t += self.get_dt() / self.fps;

        match (self.len, self.loop_pause) {
            (None, _) if self.t < 0. => {
                    self.t = 0.;
                    0
                },
            (None, _) => self.t as usize,
            (_, None) if self.t < 0. => {
                    // fixed length, but no loop, but t is negative
                    self.t = 0.;
                    0
                },
            (Some(len), None) => {
                // We have a fixed length, but we don't loop.
                let ix = self.t as usize;
                if ix >= len { len - 1 } else { ix }
            }
            (Some(len), Some(pause)) => {
                // We have a fixed length, but we don't loop.
                let loop_len = (len as f32) + pause;
                if self.t < 0. {self.t += loop_len;}
                else if self.t > loop_len {self.t -= loop_len;};
                let ix = (self.t % loop_len) as usize;
                if ix >= len { len - 1 } else { ix }
            }
        }
    }
}

#[cfg(test)]
mod test {

    #[test]
    fn timer_dts() {
        let mut t = ::Timer::new(vec!(1.,2.,4.), None);
        assert_eq!(t.get_dt(), 1.);
        assert_eq!(t.incr(), 1);
        t.faster();
        assert_eq!(t.get_dt(), 2.);
        assert_eq!(t.incr(), 3);
        t.faster();
        assert_eq!(t.get_dt(), 4.);
        assert_eq!(t.incr(), 7);
        t.faster();
        assert_eq!(t.get_dt(), 4.);
        assert_eq!(t.incr(), 11);
        t.switch_direction();
        assert_eq!(t.get_dt(), -4.);
        assert_eq!(t.incr(), 7);
        t.faster();
        assert_eq!(t.get_dt(), -4.);
        assert_eq!(t.incr(), 3);
        t.switch_direction();
        assert_eq!(t.get_dt(), 4.);
        assert_eq!(t.incr(), 7);
        t.switch_direction();
        assert_eq!(t.get_dt(), -4.);
        assert_eq!(t.incr(), 3);
        t.slower();
        assert_eq!(t.get_dt(), -2.);
        assert_eq!(t.incr(), 1);
        t.slower();
        assert_eq!(t.get_dt(), -1.);
        assert_eq!(t.incr(), 0);
        t.slower();
        assert_eq!(t.get_dt(), 0.);
        assert_eq!(t.incr(), 0);
        t.slower();
        assert_eq!(t.get_dt(), 0.);
        assert_eq!(t.incr(), 0);
    }

    #[test]
    fn timer_pauseloop() {
        let mut t = ::Timer::new(vec!(1.,2.,4.), Some(5));
        t.loop_pause = Some(5.);
        t.fps = 2.;
        t.faster();
        t.faster();
        assert_eq!(t.get_dt(), 4.);
        assert_eq!(t.incr(), 2); // t = 2.
        assert_eq!(t.incr(), 4); // t = 4.
        assert_eq!(t.incr(), 4); // t = 6.
        assert_eq!(t.incr(), 4); // t = 8.
        assert_eq!(t.incr(), 0); // t = 10.
    }
}

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

<% data.new_style_struct("InheritedBox", inherited=True, gecko_name="Visibility") %>

// TODO: collapse. Well, do tables first.
${helpers.single_keyword("visibility",
                         "visible hidden",
                         extra_gecko_values="collapse",
                         gecko_ffi_name="mVisible",
                         animation_type="normal",
                         spec="https://drafts.csswg.org/css-box/#propdef-visibility")}

// CSS Writing Modes Level 3
// https://drafts.csswg.org/css-writing-modes-3
${helpers.single_keyword("writing-mode",
                         "horizontal-tb vertical-rl vertical-lr",
                         experimental=True,
                         need_clone=True,
                         animation_type="none",
                         spec="https://drafts.csswg.org/css-writing-modes/#propdef-writing-mode")}

${helpers.single_keyword("direction", "ltr rtl", need_clone=True, animation_type="none",
                         spec="https://drafts.csswg.org/css-writing-modes/#propdef-direction")}

<%helpers:single_keyword_computed
    name="text-orientation"
    values="mixed upright sideways"
    extra_specified="sideways-right"
    products="gecko"
    need_clone="True"
    animation_type="none"
    spec="https://drafts.csswg.org/css-writing-modes/#propdef-text-orientation"
>
    use values::HasViewportPercentage;
    no_viewport_percentage!(SpecifiedValue);

    impl ToComputedValue for SpecifiedValue {
        type ComputedValue = computed_value::T;

        #[inline]
        fn to_computed_value(&self, _: &Context) -> computed_value::T {
            match *self {
                % for value in "mixed upright sideways".split():
                    SpecifiedValue::${value} => computed_value::T::${value},
                % endfor
                // https://drafts.csswg.org/css-writing-modes-3/#valdef-text-orientation-sideways-right
                SpecifiedValue::sideways_right => computed_value::T::sideways,
            }
        }

        #[inline]
        fn from_computed_value(computed: &computed_value::T) -> SpecifiedValue {
            match *computed {
                % for value in "mixed upright sideways".split():
                    computed_value::T::${value} => SpecifiedValue::${value},
                % endfor
            }
        }
    }
</%helpers:single_keyword_computed>

// CSS Color Module Level 4
// https://drafts.csswg.org/css-color/
${helpers.single_keyword("color-adjust",
                         "economy exact", products="gecko",
                         animation_type="none",
                         spec="https://drafts.csswg.org/css-color/#propdef-color-adjust")}

<% image_rendering_custom_consts = { "crisp-edges": "CRISPEDGES" } %>
// According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for `auto`
// And, firefox doesn't support `pixelated` yet (https://bugzilla.mozilla.org/show_bug.cgi?id=856337)
${helpers.single_keyword("image-rendering",
                         "auto crisp-edges",
                         extra_gecko_values="optimizespeed optimizequality",
                         extra_servo_values="pixelated",
                         custom_consts=image_rendering_custom_consts,
                         animation_type="none",
                         spec="https://drafts.csswg.org/css-images/#propdef-image-rendering")}

// Image Orientation
<%helpers:longhand name="image-orientation"
                   products="gecko"
                   animation_type="none"
    spec="https://drafts.csswg.org/css-images/#propdef-image-orientation, \
      /// additional values in https://developer.mozilla.org/en-US/docs/Web/CSS/image-orientation">
    use std::fmt;
    use style_traits::ToCss;
    use values::specified::Angle;

    use values::HasViewportPercentage;
    no_viewport_percentage!(SpecifiedValue);

    use std::f32::consts::PI;
    use values::CSSFloat;
    const TWO_PI: CSSFloat = 2.0 * PI;

    #[derive(Clone, PartialEq, Copy, Debug)]
    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
    pub struct SpecifiedValue {
        pub angle: Option<Angle>,
        pub flipped: bool
    }

    impl ToCss for SpecifiedValue {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            if let Some(angle) = self.angle {
                try!(angle.to_css(dest));
                if self.flipped {
                    dest.write_str(" flipped")
                } else {
                    Ok(())
                }
            } else {
                if self.flipped {
                    dest.write_str("flipped")
                } else {
                    dest.write_str("from-image")
                }
            }
        }
    }

    pub mod computed_value {
        use values::computed::Angle;

        #[derive(Clone, PartialEq, Copy, Debug)]
        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        pub enum T {
            FromImage,
            AngleWithFlipped(Angle, bool),
        }
    }

    #[inline]
    pub fn get_initial_value() -> computed_value::T {
        computed_value::T::AngleWithFlipped(computed::Angle::zero(), false)
    }

    // According to CSS Content Module Level 3:
    // The computed value of the property is calculated by rounding the specified angle
    // to the nearest quarter-turn, rounding away from 0, then moduloing the value by 1 turn.
    #[inline]
    fn normalize_angle(angle: &computed::Angle) -> computed::Angle {
        let radians = angle.radians();
        let rounded_quarter_turns = (4.0 * radians / TWO_PI).round();
        let normalized_quarter_turns = (rounded_quarter_turns % 4.0 + 4.0) % 4.0;
        let normalized_radians = normalized_quarter_turns/4.0 * TWO_PI;
        computed::Angle::from_radians(normalized_radians)
    }

    impl ToComputedValue for SpecifiedValue {
        type ComputedValue = computed_value::T;

        #[inline]
        fn to_computed_value(&self, context: &Context) -> computed_value::T {
            if let Some(ref angle) = self.angle {
                let angle = angle.to_computed_value(context);
                let normalized_angle = normalize_angle(&angle);
                computed_value::T::AngleWithFlipped(normalized_angle, self.flipped)
            } else {
                if self.flipped {
                    computed_value::T::AngleWithFlipped(computed::Angle::zero(), true)
                } else {
                    computed_value::T::FromImage
                }
            }
        }

        #[inline]
        fn from_computed_value(computed: &computed_value::T) -> Self {
            match *computed {
                computed_value::T::FromImage => SpecifiedValue { angle: None, flipped: false },
                computed_value::T::AngleWithFlipped(ref angle, flipped) => {
                    SpecifiedValue {
                        angle: Some(Angle::from_computed_value(angle)),
                        flipped: flipped,
                    }
                }
            }
        }
    }

    impl ToCss for computed_value::T {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            match *self {
                computed_value::T::FromImage => dest.write_str("from-image"),
                computed_value::T::AngleWithFlipped(angle, flipped) => {
                    try!(angle.to_css(dest));
                    if flipped {
                        try!(dest.write_str(" flipped"));
                    }
                    Ok(())
                },
            }
        }
    }

    // from-image | <angle> | [<angle>? flip]
    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
        if input.try(|input| input.expect_ident_matching("from-image")).is_ok() {
            // Handle from-image
            Ok(SpecifiedValue { angle: None, flipped: false })
        } else if input.try(|input| input.expect_ident_matching("flip")).is_ok() {
            // Handle flip
            Ok(SpecifiedValue { angle: Some(Angle::zero()), flipped: true })
        } else {
            // Handle <angle> | <angle> flip
            let angle = input.try(|input| Angle::parse(context, input)).ok();
            if angle.is_none() {
                return Err(());
            }

            let flipped = input.try(|input| input.expect_ident_matching("flip")).is_ok();
            Ok(SpecifiedValue { angle: angle, flipped: flipped })
        }
    }
</%helpers:longhand>

// Used in the bottom-up flow construction traversal to avoid constructing flows for
// descendants of nodes with `display: none`.
<%helpers:longhand name="-servo-under-display-none"
                   derived_from="display"
                   products="servo"
                   animation_type="none"
                   spec="Nonstandard (internal layout use only)">
    use std::fmt;
    use style_traits::ToCss;
    use values::computed::ComputedValueAsSpecified;
    use values::HasViewportPercentage;

    no_viewport_percentage!(SpecifiedValue);

    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
    #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
    pub struct SpecifiedValue(pub bool);

    pub mod computed_value {
        pub type T = super::SpecifiedValue;
    }
    impl ComputedValueAsSpecified for SpecifiedValue {}

    pub fn get_initial_value() -> computed_value::T {
        SpecifiedValue(false)
    }

    impl ToCss for SpecifiedValue {
        fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write {
            Ok(()) // Internal property
        }
    }

    #[inline]
    pub fn derive_from_display(context: &mut Context) {
        use super::display::computed_value::T as Display;

        if context.style().get_box().clone_display() == Display::none {
            context.mutate_style().mutate_inheritedbox()
                                  .set__servo_under_display_none(SpecifiedValue(true));
        }
    }
</%helpers:longhand>
<commit_msg>Auto merge of #16361 - KuoE0:bug-1354000, r=heycam<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

<% data.new_style_struct("InheritedBox", inherited=True, gecko_name="Visibility") %>

// TODO: collapse. Well, do tables first.
${helpers.single_keyword("visibility",
                         "visible hidden",
                         extra_gecko_values="collapse",
                         gecko_ffi_name="mVisible",
                         animation_type="normal",
                         spec="https://drafts.csswg.org/css-box/#propdef-visibility")}

// CSS Writing Modes Level 3
// https://drafts.csswg.org/css-writing-modes-3
${helpers.single_keyword("writing-mode",
                         "horizontal-tb vertical-rl vertical-lr",
                         experimental=True,
                         need_clone=True,
                         animation_type="none",
                         spec="https://drafts.csswg.org/css-writing-modes/#propdef-writing-mode")}

${helpers.single_keyword("direction", "ltr rtl", need_clone=True, animation_type="none",
                         spec="https://drafts.csswg.org/css-writing-modes/#propdef-direction")}

<%helpers:single_keyword_computed
    name="text-orientation"
    values="mixed upright sideways"
    extra_specified="sideways-right"
    products="gecko"
    need_clone="True"
    animation_type="none"
    spec="https://drafts.csswg.org/css-writing-modes/#propdef-text-orientation"
>
    use values::HasViewportPercentage;
    no_viewport_percentage!(SpecifiedValue);

    impl ToComputedValue for SpecifiedValue {
        type ComputedValue = computed_value::T;

        #[inline]
        fn to_computed_value(&self, _: &Context) -> computed_value::T {
            match *self {
                % for value in "mixed upright sideways".split():
                    SpecifiedValue::${value} => computed_value::T::${value},
                % endfor
                // https://drafts.csswg.org/css-writing-modes-3/#valdef-text-orientation-sideways-right
                SpecifiedValue::sideways_right => computed_value::T::sideways,
            }
        }

        #[inline]
        fn from_computed_value(computed: &computed_value::T) -> SpecifiedValue {
            match *computed {
                % for value in "mixed upright sideways".split():
                    computed_value::T::${value} => SpecifiedValue::${value},
                % endfor
            }
        }
    }
</%helpers:single_keyword_computed>

// CSS Color Module Level 4
// https://drafts.csswg.org/css-color/
${helpers.single_keyword("color-adjust",
                         "economy exact", products="gecko",
                         animation_type="none",
                         spec="https://drafts.csswg.org/css-color/#propdef-color-adjust")}

<% image_rendering_custom_consts = { "crisp-edges": "CRISPEDGES",
                                     "-moz-crisp-edges": "CRISPEDGES" } %>
// According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for `auto`
// And, firefox doesn't support `pixelated` yet (https://bugzilla.mozilla.org/show_bug.cgi?id=856337)
${helpers.single_keyword("image-rendering",
                         "auto",
                         extra_gecko_values="optimizespeed optimizequality -moz-crisp-edges",
                         extra_servo_values="pixelated crisp-edges",
                         custom_consts=image_rendering_custom_consts,
                         animation_type="none",
                         spec="https://drafts.csswg.org/css-images/#propdef-image-rendering")}

// Image Orientation
<%helpers:longhand name="image-orientation"
                   products="gecko"
                   animation_type="none"
    spec="https://drafts.csswg.org/css-images/#propdef-image-orientation, \
      /// additional values in https://developer.mozilla.org/en-US/docs/Web/CSS/image-orientation">
    use std::fmt;
    use style_traits::ToCss;
    use values::specified::Angle;

    use values::HasViewportPercentage;
    no_viewport_percentage!(SpecifiedValue);

    use std::f32::consts::PI;
    use values::CSSFloat;
    const TWO_PI: CSSFloat = 2.0 * PI;

    #[derive(Clone, PartialEq, Copy, Debug)]
    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
    pub struct SpecifiedValue {
        pub angle: Option<Angle>,
        pub flipped: bool
    }

    impl ToCss for SpecifiedValue {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            if let Some(angle) = self.angle {
                try!(angle.to_css(dest));
                if self.flipped {
                    dest.write_str(" flipped")
                } else {
                    Ok(())
                }
            } else {
                if self.flipped {
                    dest.write_str("flipped")
                } else {
                    dest.write_str("from-image")
                }
            }
        }
    }

    pub mod computed_value {
        use values::computed::Angle;

        #[derive(Clone, PartialEq, Copy, Debug)]
        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        pub enum T {
            FromImage,
            AngleWithFlipped(Angle, bool),
        }
    }

    #[inline]
    pub fn get_initial_value() -> computed_value::T {
        computed_value::T::AngleWithFlipped(computed::Angle::zero(), false)
    }

    // According to CSS Content Module Level 3:
    // The computed value of the property is calculated by rounding the specified angle
    // to the nearest quarter-turn, rounding away from 0, then moduloing the value by 1 turn.
    #[inline]
    fn normalize_angle(angle: &computed::Angle) -> computed::Angle {
        let radians = angle.radians();
        let rounded_quarter_turns = (4.0 * radians / TWO_PI).round();
        let normalized_quarter_turns = (rounded_quarter_turns % 4.0 + 4.0) % 4.0;
        let normalized_radians = normalized_quarter_turns/4.0 * TWO_PI;
        computed::Angle::from_radians(normalized_radians)
    }

    impl ToComputedValue for SpecifiedValue {
        type ComputedValue = computed_value::T;

        #[inline]
        fn to_computed_value(&self, context: &Context) -> computed_value::T {
            if let Some(ref angle) = self.angle {
                let angle = angle.to_computed_value(context);
                let normalized_angle = normalize_angle(&angle);
                computed_value::T::AngleWithFlipped(normalized_angle, self.flipped)
            } else {
                if self.flipped {
                    computed_value::T::AngleWithFlipped(computed::Angle::zero(), true)
                } else {
                    computed_value::T::FromImage
                }
            }
        }

        #[inline]
        fn from_computed_value(computed: &computed_value::T) -> Self {
            match *computed {
                computed_value::T::FromImage => SpecifiedValue { angle: None, flipped: false },
                computed_value::T::AngleWithFlipped(ref angle, flipped) => {
                    SpecifiedValue {
                        angle: Some(Angle::from_computed_value(angle)),
                        flipped: flipped,
                    }
                }
            }
        }
    }

    impl ToCss for computed_value::T {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            match *self {
                computed_value::T::FromImage => dest.write_str("from-image"),
                computed_value::T::AngleWithFlipped(angle, flipped) => {
                    try!(angle.to_css(dest));
                    if flipped {
                        try!(dest.write_str(" flipped"));
                    }
                    Ok(())
                },
            }
        }
    }

    // from-image | <angle> | [<angle>? flip]
    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
        if input.try(|input| input.expect_ident_matching("from-image")).is_ok() {
            // Handle from-image
            Ok(SpecifiedValue { angle: None, flipped: false })
        } else if input.try(|input| input.expect_ident_matching("flip")).is_ok() {
            // Handle flip
            Ok(SpecifiedValue { angle: Some(Angle::zero()), flipped: true })
        } else {
            // Handle <angle> | <angle> flip
            let angle = input.try(|input| Angle::parse(context, input)).ok();
            if angle.is_none() {
                return Err(());
            }

            let flipped = input.try(|input| input.expect_ident_matching("flip")).is_ok();
            Ok(SpecifiedValue { angle: angle, flipped: flipped })
        }
    }
</%helpers:longhand>

// Used in the bottom-up flow construction traversal to avoid constructing flows for
// descendants of nodes with `display: none`.
<%helpers:longhand name="-servo-under-display-none"
                   derived_from="display"
                   products="servo"
                   animation_type="none"
                   spec="Nonstandard (internal layout use only)">
    use std::fmt;
    use style_traits::ToCss;
    use values::computed::ComputedValueAsSpecified;
    use values::HasViewportPercentage;

    no_viewport_percentage!(SpecifiedValue);

    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
    #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
    pub struct SpecifiedValue(pub bool);

    pub mod computed_value {
        pub type T = super::SpecifiedValue;
    }
    impl ComputedValueAsSpecified for SpecifiedValue {}

    pub fn get_initial_value() -> computed_value::T {
        SpecifiedValue(false)
    }

    impl ToCss for SpecifiedValue {
        fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write {
            Ok(()) // Internal property
        }
    }

    #[inline]
    pub fn derive_from_display(context: &mut Context) {
        use super::display::computed_value::T as Display;

        if context.style().get_box().clone_display() == Display::none {
            context.mutate_style().mutate_inheritedbox()
                                  .set__servo_under_display_none(SpecifiedValue(true));
        }
    }
</%helpers:longhand>
<|endoftext|>


<commit_before>// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

//! Fuzz tests for the X.509 certificate parser, including signature
//! validation.

#![no_main]

use libfuzzer_sys::fuzz_target;

use manticore::cert::Cert;
use manticore::cert::CertFormat;
use manticore::crypto::sig;
use manticore::protocol::capabilities;

/// A `Ciphers` that blindly accepts all signatures.
struct NoVerify;

impl sig::Verify for NoVerify {
    fn verify(
        &mut self,
        _: &[&[u8]],
        _: &[u8],
    ) -> Result<(), sig::Error> {
        Ok(())
    }
}

impl sig::Ciphers for NoVerify {
    type Error = ();
    fn negotiate(&self, _: &mut capabilities::Crypto) {}
    fn verifier<'a>(
        &'a mut self,
        _: sig::Algo,
        _: &sig::PublicKeyParams,
    ) -> Option<&'a mut dyn sig::Verify> {
        Some(self)
    }
}

fuzz_target!(|data: &[u8]| {
    let _ = Cert::parse(
        data,
        CertFormat::RiotX509,
        None,
        &mut NoVerify,
    );

    // NOTE: we might actually succeed at creating a valid cert, so we can't
    // check for is_err() here.
});
<commit_msg>[fuzz] Fix broken CI again<commit_after>// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

//! Fuzz tests for the X.509 certificate parser, including signature
//! validation.

#![no_main]

use libfuzzer_sys::fuzz_target;

use manticore::cert::Cert;
use manticore::cert::CertFormat;
use manticore::crypto::sig;
use manticore::protocol::capabilities;

/// A `Ciphers` that blindly accepts all signatures.
struct NoVerify;

impl sig::Verify for NoVerify {
    fn verify(
        &mut self,
        _: &[&[u8]],
        _: &[u8],
    ) -> Result<(), sig::Error> {
        Ok(())
    }
}

impl sig::Ciphers for NoVerify {
    fn negotiate(&self, _: &mut capabilities::Crypto) {}
    fn verifier<'a>(
        &'a mut self,
        _: sig::Algo,
        _: &sig::PublicKeyParams,
    ) -> Option<&'a mut dyn sig::Verify> {
        Some(self)
    }
}

fuzz_target!(|data: &[u8]| {
    let _ = Cert::parse(
        data,
        CertFormat::RiotX509,
        None,
        &mut NoVerify,
    );

    // NOTE: we might actually succeed at creating a valid cert, so we can't
    // check for is_err() here.
});
<|endoftext|>