Complete BLT writer and implement tests for file conversions

This commit is contained in:
RunasSudo 2021-09-02 22:35:10 +10:00
parent e9e1c63c9c
commit 27ead09960
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
7 changed files with 148 additions and 12 deletions

View File

@ -29,19 +29,19 @@ use std::fs::File;
#[clap(setting=AppSettings::DeriveDisplayOrder)] #[clap(setting=AppSettings::DeriveDisplayOrder)]
pub struct SubcmdOptions { pub struct SubcmdOptions {
/// Path to the input data file /// Path to the input data file
#[clap(help_heading=Some("INPUT/OUTPUT"))] #[clap(help_heading=Some("INPUT"))]
infile: String, infile: String,
/// Path to the output data file
#[clap(help_heading=Some("INPUT/OUTPUT"))]
outfile: String,
/// Format of input file /// Format of input file
#[clap(help_heading=Some("INPUT/OUTPUT"), short, long, possible_values=&["bin", "blt", "csp"], value_name="format")] #[clap(help_heading=Some("INPUT"), short, long, possible_values=&["bin", "blt", "csp"], value_name="format")]
r#in: Option<String>, r#in: Option<String>,
/// Path to the output data file
#[clap(help_heading=Some("OUTPUT"))]
outfile: String,
/// Format of output file /// Format of output file
#[clap(help_heading=Some("INPUT/OUTPUT"), short, long, possible_values=&["bin", "blt", "csp"], value_name="format")] #[clap(help_heading=Some("OUTPUT"), short, long, possible_values=&["bin", "blt", "csp"], value_name="format")]
out: Option<String>, out: Option<String>,
/// Number of seats /// Number of seats

View File

@ -30,7 +30,7 @@ use std::fmt;
use std::ops; use std::ops;
/// Constraints for an [crate::election::Election] /// Constraints for an [crate::election::Election]
#[derive(Debug)] #[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))] #[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
pub struct Constraints(pub Vec<Constraint>); pub struct Constraints(pub Vec<Constraint>);
@ -124,7 +124,7 @@ impl Constraints {
} }
/// A single dimension of constraint /// A single dimension of constraint
#[derive(Debug)] #[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))] #[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
pub struct Constraint { pub struct Constraint {
/// Name of this constraint /// Name of this constraint
@ -134,7 +134,7 @@ pub struct Constraint {
} }
/// A group of candidates, of which a certain minimum and maximum must be elected /// A group of candidates, of which a certain minimum and maximum must be elected
#[derive(Debug)] #[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))] #[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
pub struct ConstrainedGroup { pub struct ConstrainedGroup {
/// Name of this group /// Name of this group

View File

@ -28,6 +28,7 @@ use std::cmp::max;
use std::collections::HashMap; use std::collections::HashMap;
/// An election to be counted /// An election to be counted
#[derive(Clone)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))] #[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
pub struct Election<N> { pub struct Election<N> {
/// Name of the election /// Name of the election
@ -65,7 +66,7 @@ impl<N: Number> Election<N> {
} }
/// A candidate in an [Election] /// A candidate in an [Election]
#[derive(PartialEq, Eq, Hash)] #[derive(Clone, Eq, Hash, PartialEq)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))] #[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
pub struct Candidate { pub struct Candidate {
/// Name of the candidate /// Name of the candidate
@ -368,6 +369,7 @@ pub struct Vote<'a, N> {
} }
/// A record of a voter's preferences /// A record of a voter's preferences
#[derive(Clone)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))] #[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
pub struct Ballot<N> { pub struct Ballot<N> {
/// Original value/weight of the ballot /// Original value/weight of the ballot

View File

@ -26,6 +26,11 @@ use std::path::Path;
/// Parse the given BIN file /// Parse the given BIN file
pub fn parse_path<P: AsRef<Path>, N: Number>(path: P) -> Election<N> { pub fn parse_path<P: AsRef<Path>, N: Number>(path: P) -> Election<N> {
let content = fs::read(path).expect("IO Error"); let content = fs::read(path).expect("IO Error");
return parse_bytes(&content);
}
/// Parse the given BIN file
pub fn parse_bytes<N: Number>(content: &[u8]) -> Election<N> {
let archived = unsafe { let archived = unsafe {
archived_root::<Election<N>>(&content) archived_root::<Election<N>>(&content)
}; };

View File

@ -18,6 +18,8 @@
use crate::election::Election; use crate::election::Election;
use crate::numbers::Number; use crate::numbers::Number;
use itertools::Itertools;
use std::io::{BufWriter, Write}; use std::io::{BufWriter, Write};
/// Write the [Election] into BLT format /// Write the [Election] into BLT format
@ -29,7 +31,8 @@ pub fn write<W: Write, N: Number>(election: Election<N>, output: W) {
// Write withdrawn candidates // Write withdrawn candidates
if !election.withdrawn_candidates.is_empty() { if !election.withdrawn_candidates.is_empty() {
todo!(); output.write(election.withdrawn_candidates.into_iter().map(|idx| format!("-{}", idx + 1)).join(" ").as_bytes()).expect("IO Error");
output.write(b"\n").expect("IO Error");
} }
// Write ballots // Write ballots

57
tests/convert.rs Normal file
View File

@ -0,0 +1,57 @@
/* OpenTally: Open-source election vote counting
* Copyright © 2021 Lee Yingtong Li (RunasSudo)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use opentally::election::Election;
use opentally::numbers::Rational;
use opentally::parser;
use opentally::writer;
use std::fs;
#[test]
fn ers97_wd_conversions() {
let blt_input = fs::read_to_string("tests/data/ers97_wd.blt").expect("IO Error");
let election: Election<Rational> = parser::blt::parse_path("tests/data/ers97_wd.blt").expect("Parse Error");
// Check BLT
let mut buf: Vec<u8> = Vec::new();
writer::blt::write(election.clone(), &mut buf);
assert_eq!(blt_input, std::str::from_utf8(&buf).expect("UTF-8 Error"));
// Check BIN
buf.clear();
writer::bin::write(election.clone(), &mut buf);
let election2: Election<Rational> = parser::bin::parse_bytes(&buf);
buf.clear();
writer::blt::write(election2, &mut buf);
assert_eq!(blt_input, std::str::from_utf8(&buf).expect("UTF-8 Error"));
// Check CSP
buf.clear();
writer::csp::write(election.clone(), &mut buf);
let mut election2: Election<Rational> = parser::csp::parse_reader(&buf[..]);
election2.seats = election.seats;
election2.name = election.name;
election2.withdrawn_candidates = election.withdrawn_candidates.clone();
buf.clear();
writer::blt::write(election2, &mut buf);
assert_eq!(blt_input, std::str::from_utf8(&buf).expect("UTF-8 Error"));
}

69
tests/data/ers97_wd.blt Normal file
View File

@ -0,0 +1,69 @@
11 6
-1 -2
8 1 2 0
3 1 3 0
14 1 4 0
34 1 5 0
1 1 6 0
4 1 7 0
1 1 8 0
4 1 9 3 0
3 1 9 4 0
2 1 9 6 0
3 1 9 7 0
4 1 9 8 0
9 1 9 0
4 1 10 3 0
5 1 10 4 0
3 1 10 6 0
3 1 10 7 0
6 1 10 8 0
10 1 10 0
1 1 11 5 0
1 1 11 0
11 1 0
105 2 0
91 3 0
90 4 0
81 5 0
64 6 0
11 7 6 0
36 7 8 0
12 7 0
55 8 0
3 9 3 0
2 9 4 0
2 9 5 3 0
1 9 5 4 0
15 9 5 0
2 9 6 0
2 9 8 0
2 10 3 0
2 10 4 0
2 10 5 6 0
1 10 5 7 6 0
2 10 5 8 0
11 10 5 0
1 10 6 0
1 10 7 4 0
1 10 8 0
1 10 0
2 11 2 0
4 11 3 0
1 11 4 0
5 11 7 8 0
10 11 8 0
1 11 0
0
"Smith"
"Duke"
"Prince"
"Freeman"
"Carpenter"
"Baron"
"Abbot"
"Vicar"
"Wright"
"Glazier"
"Monk"
"ERS Model - 1997 Edition"