Compare commits
5 commits
b3bccbbf65
...
7bbb2bbc19
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bbb2bbc19 | |||
| b2c032d846 | |||
| cb5873b732 | |||
| 96ea0ddab9 | |||
| 4d47530326 |
7 changed files with 135 additions and 30 deletions
|
|
@ -13,7 +13,10 @@
|
||||||
"configureZshAsDefaultShell": "true"
|
"configureZshAsDefaultShell": "true"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/rust:1": {
|
"ghcr.io/devcontainers/features/rust:1": {
|
||||||
"targets": "x86_64-unknown-linux-musl"
|
"targets": "x86_64-unknown-linux-gnu,x86_64-unknown-linux-musl"
|
||||||
|
},
|
||||||
|
"ghcr.io/lee-orr/rusty-dev-containers/cargo-binstall:0": {
|
||||||
|
"packages": "cargo-llvm-cov"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers-contrib/features/apt-get-packages:1": {
|
"ghcr.io/devcontainers-contrib/features/apt-get-packages:1": {
|
||||||
"packages": "git-flow, musl-tools"
|
"packages": "git-flow, musl-tools"
|
||||||
|
|
@ -33,7 +36,7 @@
|
||||||
// "forwardPorts": [],
|
// "forwardPorts": [],
|
||||||
|
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
"postCreateCommand": "rustup target install x86_64-unknown-linux-musl | :",
|
// "postCreateCommand": "rustup target install x86_64-unknown-linux-musl | :",
|
||||||
|
|
||||||
// Configure tool-specific properties.
|
// Configure tool-specific properties.
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
|
@ -43,7 +46,8 @@
|
||||||
},
|
},
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"mhutchie.git-graph",
|
"mhutchie.git-graph",
|
||||||
"Gruntfuggly.todo-tree"
|
"Gruntfuggly.todo-tree",
|
||||||
|
"ryanluker.vscode-coverage-gutters"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1,7 @@
|
||||||
|
# code coverage reports
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
|
||||||
# https://github.com/github/gitignore/raw/refs/heads/main/Rust.gitignore
|
# https://github.com/github/gitignore/raw/refs/heads/main/Rust.gitignore
|
||||||
|
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
|
|
|
||||||
54
.vscode/tasks.json
vendored
54
.vscode/tasks.json
vendored
|
|
@ -5,9 +5,6 @@
|
||||||
"label": "Build Project",
|
"label": "Build Project",
|
||||||
"type": "cargo",
|
"type": "cargo",
|
||||||
"command": "build",
|
"command": "build",
|
||||||
// "presentation": {
|
|
||||||
// "reveal": "silent"
|
|
||||||
// },
|
|
||||||
"problemMatcher": "$rustc",
|
"problemMatcher": "$rustc",
|
||||||
"group": "build"
|
"group": "build"
|
||||||
},
|
},
|
||||||
|
|
@ -73,13 +70,62 @@
|
||||||
// "problemMatcher": "$rustc",
|
// "problemMatcher": "$rustc",
|
||||||
// "group": "test"
|
// "group": "test"
|
||||||
// },
|
// },
|
||||||
|
{
|
||||||
|
"label": "Test Coverage",
|
||||||
|
"hide": true,
|
||||||
|
"type": "cargo",
|
||||||
|
"command": "llvm-cov",
|
||||||
|
"args": [],
|
||||||
|
"problemMatcher": "$rustc",
|
||||||
|
"group": "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Report Coverage (html)",
|
||||||
|
"hide": true,
|
||||||
|
"type": "cargo",
|
||||||
|
"command": "llvm-cov",
|
||||||
|
"args": [
|
||||||
|
"report",
|
||||||
|
"--html",
|
||||||
|
"--output-dir" ,
|
||||||
|
"coverage",
|
||||||
|
],
|
||||||
|
"problemMatcher": "$rustc",
|
||||||
|
"group": "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Report Coverage (lcov)",
|
||||||
|
"hide": true,
|
||||||
|
"type": "cargo",
|
||||||
|
"command": "llvm-cov",
|
||||||
|
"args": [
|
||||||
|
"report",
|
||||||
|
"--lcov",
|
||||||
|
"--output-path" ,
|
||||||
|
"coverage/lcov.info",
|
||||||
|
],
|
||||||
|
"problemMatcher": "$rustc",
|
||||||
|
"group": "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Run Coverage",
|
||||||
|
"type": "shell",
|
||||||
|
"dependsOn": [
|
||||||
|
"Test Coverage",
|
||||||
|
"Report Coverage (html)",
|
||||||
|
"Report Coverage (lcov)",
|
||||||
|
],
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"group": "test"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "Run All Tests",
|
"label": "Run All Tests",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "echo All Tests successful!",
|
"command": "echo All Tests successful!",
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"Run Unit Tests",
|
"Run Unit Tests",
|
||||||
// "Run Integration Tests"
|
// "Run Integration Tests",
|
||||||
|
"Run Coverage",
|
||||||
],
|
],
|
||||||
"dependsOrder": "sequence",
|
"dependsOrder": "sequence",
|
||||||
"group": "test"
|
"group": "test"
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ impl CacheFile {
|
||||||
.take()
|
.take()
|
||||||
.expect("abort_upload called while not uploading");
|
.expect("abort_upload called while not uploading");
|
||||||
|
|
||||||
self.files.push_front(upl.into());
|
self.files.push_front(upl.stop());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn share_notify(&self, client: &impl Client) -> crate::Result<()> {
|
pub fn share_notify(&self, client: &impl Client) -> crate::Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -115,20 +115,27 @@ impl FileTrait for Checked {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use tempfile::TempDir;
|
use tempfile::{NamedTempFile, TempDir};
|
||||||
|
|
||||||
use crate::test_util::{
|
use crate::test_util::{
|
||||||
MockClient, create_file,
|
MockClient, check_trait, create_file,
|
||||||
data::{HASHES_STD_GOOD, cases, data},
|
data::{HASHES_STD_GOOD, cases, data},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn create_checked(content: &[u8]) -> (Checked, NamedTempFile) {
|
||||||
|
let file = create_file(content);
|
||||||
|
let chk = Checked::new(file.path()).expect("creating `Checked` should succeed");
|
||||||
|
|
||||||
|
// return both, so the `NamedTempFile` is not auto-deleted here
|
||||||
|
(chk, file)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_on_existing_file_works() {
|
fn new_on_existing_file_works() {
|
||||||
for (content, size) in cases() {
|
for (content, size) in cases() {
|
||||||
let file = create_file(content);
|
let (chk, file) = create_checked(content);
|
||||||
let chk = Checked::new(file.path()).expect("creating `Checked` should succeed");
|
|
||||||
|
|
||||||
let path = file
|
let path = file
|
||||||
.path()
|
.path()
|
||||||
|
|
@ -145,6 +152,19 @@ mod tests {
|
||||||
file.path().file_name().expect("`file_name` should succeed")
|
file.path().file_name().expect("`file_name` should succeed")
|
||||||
);
|
);
|
||||||
assert_eq!(chk.get_size(), size);
|
assert_eq!(chk.get_size(), size);
|
||||||
|
|
||||||
|
check_trait(
|
||||||
|
chk.as_ref(),
|
||||||
|
path.as_os_str().as_encoded_bytes(),
|
||||||
|
"AsRef<u8>",
|
||||||
|
"Checked",
|
||||||
|
);
|
||||||
|
|
||||||
|
// new_direct
|
||||||
|
let chk = Checked::new_direct(chk.path, chk.size, chk.hash);
|
||||||
|
assert_eq!(chk.path, path);
|
||||||
|
assert_eq!(chk.size, size);
|
||||||
|
assert!(chk.hash.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,8 +199,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn hashing_works() {
|
fn hashing_works() {
|
||||||
for (content, hash) in data().zip(HASHES_STD_GOOD) {
|
for (content, hash) in data().zip(HASHES_STD_GOOD) {
|
||||||
let file = create_file(content);
|
let (mut chk, _file) = create_checked(content);
|
||||||
let mut chk = Checked::new(file.path()).expect("creating `Checked` should succeed");
|
|
||||||
|
|
||||||
chk.hash(drop).expect("`hash` should succeed");
|
chk.hash(drop).expect("`hash` should succeed");
|
||||||
// `FileTrait`
|
// `FileTrait`
|
||||||
|
|
@ -193,10 +212,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn hashing_again_errors() {
|
fn hashing_again_errors() {
|
||||||
for content in data() {
|
for content in data() {
|
||||||
let file = create_file(content);
|
let (mut chk, _file) = create_checked(content);
|
||||||
let mut chk = Checked::new(file.path()).expect("creating `Checked` should succeed");
|
|
||||||
|
|
||||||
chk.hash(drop).expect("`hash` should succeed");
|
// fake hash
|
||||||
|
chk.hash = Some(String::default());
|
||||||
let err = chk.hash(drop).expect_err("`hash` twice should fail");
|
let err = chk.hash(drop).expect_err("`hash` twice should fail");
|
||||||
|
|
||||||
assert!(err.is_mismatch("unhashed file", chk.path.display().to_string()));
|
assert!(err.is_mismatch("unhashed file", chk.path.display().to_string()));
|
||||||
|
|
@ -209,8 +228,7 @@ mod tests {
|
||||||
let share_id = client.add_share();
|
let share_id = client.add_share();
|
||||||
|
|
||||||
for content in data() {
|
for content in data() {
|
||||||
let file = create_file(content);
|
let (chk, _file) = create_checked(content);
|
||||||
let chk = Checked::new(file.path()).expect("creating `Checked` should succeed");
|
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
chk.start_upload(
|
chk.start_upload(
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,19 @@
|
||||||
use std::fmt;
|
use std::{any, fmt};
|
||||||
|
|
||||||
use crate::sharry;
|
use crate::sharry;
|
||||||
|
|
||||||
|
/// Chunk of binary data belonging to a currently uploading file
|
||||||
pub struct Chunk<'t> {
|
pub struct Chunk<'t> {
|
||||||
|
/// id of the associated file
|
||||||
file_id: sharry::FileID,
|
file_id: sharry::FileID,
|
||||||
|
/// offset of this chunk in bytes
|
||||||
offset: u64,
|
offset: u64,
|
||||||
|
/// data inside this chunk
|
||||||
data: &'t [u8],
|
data: &'t [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Chunk<'_> {
|
impl fmt::Debug for Chunk<'_> {
|
||||||
|
// chunks are 1 MiB or more, we shouldn't print that
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Chunk")
|
f.debug_struct("Chunk")
|
||||||
.field("file_id", &self.file_id)
|
.field("file_id", &self.file_id)
|
||||||
|
|
@ -18,6 +23,22 @@ impl fmt::Debug for Chunk<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// convert usize into other type
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - if the given value does not fit into the target type
|
||||||
|
fn from_usize_or_panic<I>(value: usize) -> I
|
||||||
|
where
|
||||||
|
I: TryFrom<usize>,
|
||||||
|
I::Error: std::error::Error,
|
||||||
|
{
|
||||||
|
value.try_into().unwrap_or_else(|e| {
|
||||||
|
let target_type = any::type_name::<I>();
|
||||||
|
panic!("usize={value:?} did not fit into {target_type:?}: {e}")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl<'t> Chunk<'t> {
|
impl<'t> Chunk<'t> {
|
||||||
pub(super) fn new_direct(file_id: sharry::FileID, offset: u64, data: &'t [u8]) -> Self {
|
pub(super) fn new_direct(file_id: sharry::FileID, offset: u64, data: &'t [u8]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -44,12 +65,10 @@ impl<'t> Chunk<'t> {
|
||||||
|
|
||||||
/// get the chunk's length
|
/// get the chunk's length
|
||||||
pub fn get_length(&self) -> u64 {
|
pub fn get_length(&self) -> u64 {
|
||||||
let len = self.data.len();
|
// BOOKMARK this might **panic** on (hypothetical) platforms where `usize` has more than 64 bit.
|
||||||
|
|
||||||
// BOOKMARK this might **panic** on platforms where `usize` has more than 64 bit.
|
|
||||||
// Also, you've allocated more than 2 EiB ... in ONE chunk.
|
// Also, you've allocated more than 2 EiB ... in ONE chunk.
|
||||||
// Whoa! Maybe just chill?
|
// Whoa! Maybe just chill?
|
||||||
u64::try_from(len).unwrap_or_else(|e| panic!("usize={len} did not fit into u64: {e}"))
|
from_usize_or_panic(self.data.len())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,4 +104,17 @@ mod tests {
|
||||||
assert_eq!(chunk.get_length(), len);
|
assert_eq!(chunk.get_length(), len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "did not fit into \"u32\"")]
|
||||||
|
fn test_usize_overflow_panics() {
|
||||||
|
// works
|
||||||
|
assert_eq!(from_usize_or_panic::<u64>(usize::MAX), u64::MAX);
|
||||||
|
assert_eq!(from_usize_or_panic::<u32>(u32::MAX as usize), u32::MAX);
|
||||||
|
assert_eq!(from_usize_or_panic::<u16>(u16::MAX as usize), u16::MAX);
|
||||||
|
assert_eq!(from_usize_or_panic::<u8>(u8::MAX as usize), u8::MAX);
|
||||||
|
|
||||||
|
// panics
|
||||||
|
from_usize_or_panic::<u32>(usize::MAX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,6 @@ pub struct Uploading {
|
||||||
offset: u64,
|
offset: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Uploading> for Checked {
|
|
||||||
fn from(value: Uploading) -> Self {
|
|
||||||
Self::new_direct(value.path, value.size, value.hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Uploading {
|
impl Uploading {
|
||||||
pub(super) fn new_direct(
|
pub(super) fn new_direct(
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
|
@ -118,6 +112,13 @@ impl Uploading {
|
||||||
Err(self.path)
|
Err(self.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// stop uploading this file
|
||||||
|
///
|
||||||
|
/// - consume self, returning as a `file::Checked`
|
||||||
|
pub fn stop(self) -> Checked {
|
||||||
|
Checked::new_direct(self.path, self.size, self.hash)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileTrait for Uploading {
|
impl FileTrait for Uploading {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue