Skip to content

Regression in done-0.0.0-reserve on Rust 1.17 #40192

Closed
@brson

Description

@brson
brian@ip-10-145-43-250:/mnt2/dev/strcursor⟫ rustc +nightly -Vv
rustc 1.17.0-nightly (be760566c 2017-02-28)
binary: rustc
commit-hash: be760566cf938d11d34c2f6bd90d8fd0f67c2344
commit-date: 2017-02-28
host: x86_64-unknown-linux-gnu
release: 1.17.0-nightly
LLVM version: 3.9

The source is hard to acquire so here's an equivalent test case:

mod list {
    use task::Task;

    use std::fmt;

    pub struct List {
        pub tasks: Vec<Task>,
    }

    impl List {
        pub fn to_plaintext(&mut self) -> String {
            self.tasks.sort();
            self.tasks.iter()
                .map(|task| task.to_plaintext())
                .collect::<Vec<_>>()
                .join("\n")
        }
    }

    impl fmt::Display for List {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            let mut list_string = String::new();
            list_string.push_str("List(\n");
            for task in self.tasks.iter() {
                list_string.push_str(format!("{}", task).as_str());
            }
            list_string.push_str("\n)");
            write!(f, "{}", list_string)
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;
        use task::*;

        #[test]
        fn test_list_to_plaintext() {
            let mut list = List {
                tasks: vec![
                    Task { name: "done".to_string(), state: TaskState::Done },
                    Task { name: "blocked".to_string(), state: TaskState::Blocked },
                    Task { name: "backlog".to_string(), state: TaskState::Backlog },
                    Task { name: "current".to_string(), state: TaskState::Current },
                ],
            };

            // Should sort and return in correct order
            let expected_list_str =
                "~ current\n\
                 - backlog\n\
                 = blocked\n\
                 + done";

            assert_eq!(list.to_plaintext(), expected_list_str);
        }
    }
}

mod task {
    use std::cmp::Ordering;
    use std::error::Error;
    use std::fmt;

    #[derive(Debug,PartialEq)]
    pub enum TaskError {
        InvalidState(char),
        Malformed,
    }

    impl fmt::Display for TaskError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            let description = self.description();

            match *self {
                TaskError::InvalidState(token) => write!(
                    f,
                    "{}: {}",
                    description,
                    token),
                TaskError::Malformed => write!(
                    f,
                    "{}",
                    description),
            }
        }
    }

    impl Error for TaskError {
        fn description(&self) -> &str {
            match *self {
                TaskError::InvalidState(_) => "Invalid token for task state",
                TaskError::Malformed => "Malformed definition",
            }
        }

        fn cause(&self) -> Option<&Error> {
            match *self {
                TaskError::InvalidState(_) => None,
                TaskError::Malformed => None,
            }
        }
    }

    #[derive(Debug,Eq,PartialEq,PartialOrd)]
    pub enum TaskState {
        Current,
        Backlog,
        Blocked,
        Done,
    }

    impl TaskState {
        pub fn from_char(c: char) -> Result<TaskState, TaskError> {
            match c {
                '~' => Result::Ok(TaskState::Current),
                '-' => Result::Ok(TaskState::Backlog),
                '=' => Result::Ok(TaskState::Blocked),
                '+' => Result::Ok(TaskState::Done),
                token @ _ => Result::Err(TaskError::InvalidState(token))
            }
        }

        pub fn to_char(&self) -> char {
            match *self {
                TaskState::Current => '~',
                TaskState::Backlog => '-',
                TaskState::Blocked => '=',
                TaskState::Done    => '+',
            }
        }

        fn order_value(&self) -> u8 {
            match *self {
                TaskState::Current => 0,
                TaskState::Backlog => 1,
                TaskState::Blocked => 2,
                TaskState::Done    => 3,
            }
        }
    }

    impl Ord for TaskState {
        fn cmp(&self, other: &TaskState) -> Ordering {
            self.order_value().cmp(&other.order_value())
        }
    }

    #[derive(Debug,Eq,PartialEq,PartialOrd)]
    pub struct Task {
        pub name: String,
        pub state: TaskState,
    }

    impl Task {
        pub fn new(task_str: &str) -> Result<Task, TaskError> {
            let mut lines = task_str.lines();
            if lines.clone().count() == 0 {
                return Result::Err(TaskError::Malformed)
            }

            // First line should always be the task name
            let main_line = lines.next().unwrap();
            let mut main_line_chars = main_line.chars();

            // First character must be symbol for state
            let state = try!(TaskState::from_char(main_line_chars.next().unwrap()));

            let mut passed_leading_whitespace = false;
            let mut name = String::new();

            for next_char in main_line_chars {
                // Skip the leading whitespace
                if !passed_leading_whitespace {
                    if next_char.is_whitespace() {
                        continue;
                    } else {
                        passed_leading_whitespace = true;
                    }
                }

                name.push(next_char)
            }

            Result::Ok(Task {
                name: name,
                state: state,
            })
        }

        pub fn to_plaintext(&self) -> String {
            format!("{} {}", self.state.to_char(), self.name)
        }
    }

    impl fmt::Display for Task {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "Task(state: {}, name: {})", self.state.to_char(), self.name)
        }
    }

    impl Ord for Task {
        fn cmp(&self, other: &Task) -> Ordering {
            let state_order = self.state.cmp(&other.state);

            match state_order {
                Ordering::Equal => self.name.cmp(&other.name),
                _ => state_order,
            }
        }
    }

    #[cfg(test)]
    mod tests {
        use std::cmp::Ordering;
        use std::collections::HashMap;
        use super::*;

        #[test]
        fn test_task_state_to_char() {
            assert_eq!(TaskState::Current.to_char(), '~');
            assert_eq!(TaskState::Backlog.to_char(), '-');
            assert_eq!(TaskState::Blocked.to_char(), '=');
            assert_eq!(TaskState::Done.to_char(), '+');
        }

        #[test]
        fn test_task_state_from_char() {
            assert_eq!(TaskState::from_char('~').unwrap(), TaskState::Current);
            assert_eq!(TaskState::from_char('-').unwrap(), TaskState::Backlog);
            assert_eq!(TaskState::from_char('=').unwrap(), TaskState::Blocked);
            assert_eq!(TaskState::from_char('+').unwrap(), TaskState::Done);
            assert!(TaskState::from_char('!').is_err());
        }

        #[test]
        fn test_task_state_ord() {
            assert_eq!(TaskState::Current.cmp(&TaskState::Backlog), Ordering::Less);
            assert_eq!(TaskState::Backlog.cmp(&TaskState::Blocked), Ordering::Less);
            assert_eq!(TaskState::Blocked.cmp(&TaskState::Done), Ordering::Less);
        }

        #[test]
        fn test_task_new_states() {
            let mut valid_tasks = HashMap::new();
            valid_tasks.insert("~ current task", Task {
                name: "current task".to_string(),
                state: TaskState::Current,
            });
            valid_tasks.insert("- backlog task", Task {
                name: "backlog task".to_string(),
                state: TaskState::Backlog,
            });
            valid_tasks.insert("= blocked task", Task {
                name: "blocked task".to_string(),
                state: TaskState::Blocked,
            });
            valid_tasks.insert("+ done task", Task {
                name: "done task".to_string(),
                state: TaskState::Done,
            });

            for (task_str, task) in valid_tasks {
                assert_eq!(Task::new(task_str).unwrap(), task);
            }

            let mut invalid_tasks = HashMap::new();
            invalid_tasks.insert("! task name", TaskError::InvalidState('!'));
            invalid_tasks.insert("", TaskError::Malformed);

            for (task_str, task) in invalid_tasks {
                assert_eq!(Task::new(task_str).err().unwrap(), task);
            }
        }

        #[test]
        fn test_task_to_plaintext() {
            let task = Task {
                name: "task name".to_string(),
                state: TaskState::Current,
            };

            assert_eq!(task.to_plaintext(), "~ task name");
        }

        #[test]
        fn test_task_ord() {
            let current_task = Task { name: "current".to_string(), state: TaskState::Current };
            let backlog_task = Task { name: "backlog".to_string(), state: TaskState::Backlog };
            let blocked_task = Task { name: "blocked".to_string(), state: TaskState::Blocked };
            let done_task = Task { name: "done".to_string(), state: TaskState::Done };

            assert_eq!(current_task.cmp(&backlog_task), Ordering::Less);
            assert_eq!(backlog_task.cmp(&blocked_task), Ordering::Less);
            assert_eq!(blocked_task.cmp(&done_task), Ordering::Less);
        }
    }
}

brian@ip-10-145-43-250:~/dev⟫ ./test

running 7 tests
test task::tests::test_task_ord ... ok
test task::tests::test_task_new_states ... ok
test task::tests::test_task_state_from_char ... ok
test list::tests::test_list_to_plaintext ... FAILED
test task::tests::test_task_state_ord ... ok
test task::tests::test_task_state_to_char ... ok
test task::tests::test_task_to_plaintext ... ok

failures:

---- list::tests::test_list_to_plaintext stdout ----
        thread 'list::tests::test_list_to_plaintext' panicked at 'assertion failed: `(left == right)` (left: `"- backlog\n= blocked\n~ current\n+ done"`, right: `"~ current\n- backlog\n= blocked\n+ done"`)', test.rs:55
note: Run with `RUST_BACKTRACE=1` for a backtrace.


failures:
    list::tests::test_list_to_plaintext

test result: FAILED. 6 passed; 1 failed; 0 ignored; 0 measured

Not on 1.16.

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.regression-from-stable-to-nightlyPerformance or correctness regression from stable to nightly.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions