basic download facility with SHA1 verification
This commit is contained in:
commit
16f366e542
12 changed files with 1876 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
*.iml
|
||||||
|
.idea/
|
||||||
|
target/
|
||||||
1385
Cargo.lock
generated
Normal file
1385
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "jargo"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
toml = "0.8"
|
||||||
|
anyhow = "1.0"
|
||||||
|
strong-xml = "0.6"
|
||||||
|
reqwest = {version = "0.11", features = ["blocking"]}
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
bytes = "1.5"
|
||||||
|
home = "0.5"
|
||||||
|
sha1 = "0.10"
|
||||||
|
hex = "0.4"
|
||||||
22
src/config.rs
Normal file
22
src/config.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub cache_location: String,
|
||||||
|
pub maven_central: String,
|
||||||
|
pub user_home: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static CONFIG: OnceLock<Config> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn get_config() -> &'static Config {
|
||||||
|
CONFIG.get_or_init(|| {
|
||||||
|
let user_home = home::home_dir().unwrap();
|
||||||
|
Config {
|
||||||
|
cache_location: format!("{}/jargo/repo", user_home.to_str().unwrap()).into(),
|
||||||
|
user_home,
|
||||||
|
maven_central: "https://repo.maven.apache.org/maven2".into()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
CONFIG.get().unwrap()
|
||||||
|
}
|
||||||
115
src/deploader.rs
Normal file
115
src/deploader.rs
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Error};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
|
use strong_xml::XmlRead;
|
||||||
|
|
||||||
|
use crate::Artifact;
|
||||||
|
use crate::config::get_config;
|
||||||
|
use crate::pom::model::Pom;
|
||||||
|
|
||||||
|
pub fn load_artifacts(artifacts: &[Artifact]) -> Result<(), Error> {
|
||||||
|
for art in artifacts {
|
||||||
|
load_artifact(art)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_artifact(art: &Artifact) -> Result<(), Error> {
|
||||||
|
let artifact_path = format!("{}/{}/{}",
|
||||||
|
art.group.replace(".", "/"),
|
||||||
|
art.name,
|
||||||
|
art.version,
|
||||||
|
);
|
||||||
|
let local_artifact_loc = format!("{}/{}", get_config().cache_location, artifact_path);
|
||||||
|
if !exists(&local_artifact_loc) {
|
||||||
|
fs::create_dir_all(&local_artifact_loc)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_artifact_jar_path = format!("{}/{}-{}.jar", local_artifact_loc, art.name, art.version);
|
||||||
|
if !exists(&local_artifact_jar_path) {
|
||||||
|
load_verify_artifact_jar(art, &artifact_path, &local_artifact_jar_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_artifact_pom_path = format!("{}/{}-{}.pom", local_artifact_loc, art.name, art.version);
|
||||||
|
let pom_xml = if !exists(&local_artifact_pom_path) {
|
||||||
|
// download pom
|
||||||
|
let remote_artifact_pom_url = format!("{}/{}/{}-{}.pom", get_config().maven_central, artifact_path, art.name, art.version);
|
||||||
|
println!("Downloading {}", remote_artifact_pom_url);
|
||||||
|
let body = reqwest::blocking::get(&remote_artifact_pom_url)?.text().unwrap();
|
||||||
|
println!("Downloaded {}", remote_artifact_pom_url);
|
||||||
|
write_text(&local_artifact_pom_path, &body)?;
|
||||||
|
body
|
||||||
|
} else {
|
||||||
|
// read local pom file
|
||||||
|
let mut file = File::open(local_artifact_pom_path)?;
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents)?;
|
||||||
|
contents
|
||||||
|
};
|
||||||
|
|
||||||
|
// parse pom file
|
||||||
|
let pom = Pom::from_str(&pom_xml).unwrap();
|
||||||
|
|
||||||
|
let dependencies: Vec<Artifact> = pom.dependencies.value.into_iter().map(|d| d.into()).collect();
|
||||||
|
load_artifacts(&dependencies)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_verify_artifact_jar(art: &Artifact, artifact_path: &str, local_artifact_jar_path: &str) -> Result<(), Error> {
|
||||||
|
let remote_artifact_jar_url = format!("{}/{}/{}-{}.jar", get_config().maven_central, artifact_path, art.name, art.version);
|
||||||
|
|
||||||
|
println!("Downloading {}", remote_artifact_jar_url);
|
||||||
|
let jar = reqwest::blocking::get(&remote_artifact_jar_url)?.bytes().unwrap();
|
||||||
|
println!("Downloaded {}", remote_artifact_jar_url);
|
||||||
|
write_bytes(&local_artifact_jar_path, jar.clone())?;
|
||||||
|
|
||||||
|
let local_artifact_jar_sha1_path = format!("{}.sha1", local_artifact_jar_path);
|
||||||
|
|
||||||
|
|
||||||
|
// verify jarfile with SHA1 checksum
|
||||||
|
let checksum = hex::decode(
|
||||||
|
if !exists(&local_artifact_jar_sha1_path) {
|
||||||
|
let remote_artifact_jar_sha1_url = format!("{}.sha1", remote_artifact_jar_url);
|
||||||
|
println!("{}", remote_artifact_jar_sha1_url);
|
||||||
|
let jar_checksum = reqwest::blocking::get(&remote_artifact_jar_sha1_url)?.bytes().unwrap();
|
||||||
|
write_bytes(&local_artifact_jar_sha1_path, jar_checksum.clone())?;
|
||||||
|
jar_checksum.to_vec()
|
||||||
|
} else {
|
||||||
|
let mut file = File::open(local_artifact_jar_sha1_path)?;
|
||||||
|
let mut contents = Vec::new();
|
||||||
|
file.read_to_end(&mut contents)?;
|
||||||
|
contents
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut hasher = Sha1::new();
|
||||||
|
hasher.update(&jar.to_vec());
|
||||||
|
let result = hasher.finalize();
|
||||||
|
let validated = result[..] == checksum;
|
||||||
|
|
||||||
|
if !validated {
|
||||||
|
Err(anyhow!("SHA1 checksum for {} is not valid", remote_artifact_jar_url))
|
||||||
|
} else { Ok(()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists(path: &str) -> bool {
|
||||||
|
Path::new(path).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn write_bytes(jar_path: &str, bytes: Bytes) -> Result<(), Error> {
|
||||||
|
let mut file = File::create(jar_path)?;
|
||||||
|
file.write_all(&bytes)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_text(path: &str, contents: &String) -> Result<(), Error> {
|
||||||
|
let mut file = File::create(path)?;
|
||||||
|
file.write(contents.as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
83
src/lib.rs
Normal file
83
src/lib.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Error};
|
||||||
|
use toml::{Table, Value};
|
||||||
|
|
||||||
|
pub mod pom;
|
||||||
|
mod config;
|
||||||
|
pub mod deploader;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Project {
|
||||||
|
pub group: String,
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
pub dependencies: Vec<Artifact>,
|
||||||
|
pub test_dependencies: Vec<Artifact>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Artifact {
|
||||||
|
pub group: String,
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<pom::model::Dependency> for Artifact {
|
||||||
|
fn from(value: pom::model::Dependency) -> Self {
|
||||||
|
Artifact {
|
||||||
|
group: value.group_id.value,
|
||||||
|
name: value.artifact_id.value,
|
||||||
|
version: value.version.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Artifact {
|
||||||
|
pub fn from_table_entry(name_group: &str, version: String) -> Result<Self, Error>{
|
||||||
|
let name_group_split: Vec<&str> = name_group.split(":").collect();
|
||||||
|
if 2 != name_group_split.len(){
|
||||||
|
return Err(anyhow!("dependency {} not well formatted", name_group));
|
||||||
|
}
|
||||||
|
let group = name_group_split[0].into();
|
||||||
|
let name = name_group_split[1].into();
|
||||||
|
|
||||||
|
Ok(Self{
|
||||||
|
group,
|
||||||
|
name,
|
||||||
|
version: version[1..version.len()-1].to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_project(jargo_file: Option<&str>) -> Result<Project, Error> {
|
||||||
|
let project_table = fs::read_to_string(jargo_file.unwrap_or("./Jargo.toml"))?.parse::<Table>()?;
|
||||||
|
let package = project_table.get("package").expect("package info missing");
|
||||||
|
|
||||||
|
let dependencies = get_dependencies(project_table.get("dependencies"))?;
|
||||||
|
|
||||||
|
let test_dependencies = get_dependencies(project_table.get("test-dependencies"))?;
|
||||||
|
|
||||||
|
Ok(Project {
|
||||||
|
group: package.get("group").unwrap().to_string(),
|
||||||
|
name: package.get("name").unwrap().to_string(),
|
||||||
|
version: package.get("version").unwrap().to_string(),
|
||||||
|
dependencies,
|
||||||
|
test_dependencies,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_dependencies(table: Option<&Value>) -> Result<Vec<Artifact>, Error> {
|
||||||
|
let mut dependencies = vec![];
|
||||||
|
if let Some(table) = table {
|
||||||
|
let table = table.as_table();
|
||||||
|
if let Some(table) = table {
|
||||||
|
for dep in table {
|
||||||
|
dependencies.push(
|
||||||
|
Artifact::from_table_entry(dep.0, dep.1.to_string())?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(dependencies)
|
||||||
|
}
|
||||||
7
src/main.rs
Normal file
7
src/main.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
use anyhow::Error;
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<(), Error> {
|
||||||
|
let project = jargo::load_project(Some("tests/sample_project/Jargo.toml"))?;
|
||||||
|
jargo::deploader::load_artifacts(&project.test_dependencies)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
1
src/pom/mod.rs
Normal file
1
src/pom/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod model;
|
||||||
212
src/pom/model.rs
Normal file
212
src/pom/model.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
use strong_xml::{XmlRead};
|
||||||
|
|
||||||
|
/// The Maven variant to parse poms
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "project")]
|
||||||
|
pub struct Pom {
|
||||||
|
#[xml(child = "modelVersion")]
|
||||||
|
pub model_version: ModelVersion,
|
||||||
|
#[xml(child = "groupId")]
|
||||||
|
pub group_id: GroupId,
|
||||||
|
#[xml(child = "artifactId")]
|
||||||
|
pub artifact_id: ArtifactId,
|
||||||
|
#[xml(child = "version")]
|
||||||
|
pub version: Version,
|
||||||
|
#[xml(child = "name")]
|
||||||
|
pub name: Name,
|
||||||
|
#[xml(child = "packaging")]
|
||||||
|
pub packaging: Packaging,
|
||||||
|
#[xml(child = "url")]
|
||||||
|
pub url: Url,
|
||||||
|
#[xml(child = "description")]
|
||||||
|
pub description: Description,
|
||||||
|
#[xml(child = "licenses")]
|
||||||
|
pub licences: Licenses,
|
||||||
|
#[xml(child = "scm")]
|
||||||
|
pub scm: Scm,
|
||||||
|
#[xml(child = "developers")]
|
||||||
|
pub developers: Developers,
|
||||||
|
#[xml(child = "dependencies")]
|
||||||
|
pub dependencies: Dependencies,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "modelVersion")]
|
||||||
|
pub struct ModelVersion {
|
||||||
|
#[xml(text)]
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "groupId")]
|
||||||
|
pub struct GroupId {
|
||||||
|
#[xml(text)]
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "artifactId")]
|
||||||
|
pub struct ArtifactId {
|
||||||
|
#[xml(text)]
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "version")]
|
||||||
|
pub struct Version {
|
||||||
|
#[xml(text)]
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "name")]
|
||||||
|
pub struct Name {
|
||||||
|
#[xml(text)]
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "id")]
|
||||||
|
pub struct Id {
|
||||||
|
#[xml(text)]
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "packaging")]
|
||||||
|
pub struct Packaging {
|
||||||
|
#[xml(text)]
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "url")]
|
||||||
|
pub struct Url {
|
||||||
|
#[xml(text)]
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "description")]
|
||||||
|
pub struct Description {
|
||||||
|
#[xml(text)]
|
||||||
|
value:String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "licenses")]
|
||||||
|
pub struct Licenses {
|
||||||
|
#[xml(child = "license")]
|
||||||
|
licenses: Vec<License>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "distribution")]
|
||||||
|
pub struct Distribution {
|
||||||
|
#[xml(text)]
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "license")]
|
||||||
|
pub struct License {
|
||||||
|
#[xml(child = "name")]
|
||||||
|
name: Name,
|
||||||
|
#[xml(child = "url")]
|
||||||
|
url: Url,
|
||||||
|
#[xml(child = "distribution")]
|
||||||
|
distribution: Option<Distribution>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "scm")]
|
||||||
|
pub struct Scm {
|
||||||
|
#[xml(child = "url")]
|
||||||
|
url: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "developers")]
|
||||||
|
pub struct Developers {
|
||||||
|
#[xml(child = "developer")]
|
||||||
|
developers: Vec<Developer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "developer")]
|
||||||
|
struct Developer {
|
||||||
|
#[xml(child = "id")]
|
||||||
|
id: Option<Id>,
|
||||||
|
#[xml(child = "name")]
|
||||||
|
name: Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "dependencies")]
|
||||||
|
pub struct Dependencies {
|
||||||
|
#[xml(child = "developer")]
|
||||||
|
pub value: Vec<Dependency>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(XmlRead, PartialEq, Debug)]
|
||||||
|
#[xml(tag = "dependency")]
|
||||||
|
pub struct Dependency {
|
||||||
|
#[xml(child = "groupId")]
|
||||||
|
pub group_id: GroupId,
|
||||||
|
#[xml(child = "artifactId")]
|
||||||
|
pub artifact_id: ArtifactId,
|
||||||
|
#[xml(child = "version")]
|
||||||
|
pub version: Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use strong_xml::XmlRead;
|
||||||
|
|
||||||
|
use crate::pom::model::Pom;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_should_not_fail() {
|
||||||
|
Pom::from_str(r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd ">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<version>1.9.5</version>
|
||||||
|
<name>Mockito</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<url>http://www.mockito.org</url>
|
||||||
|
<description>Mock objects library for java</description>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>The MIT License</name>
|
||||||
|
<url>http://code.google.com/p/mockito/wiki/License</url>
|
||||||
|
<distribution>repo</distribution>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
<scm>
|
||||||
|
<url>http://code.google.com/p/mockito/source/browse/</url>
|
||||||
|
</scm>
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>szczepiq</id>
|
||||||
|
<name>Szczepan Faber</name>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hamcrest</groupId>
|
||||||
|
<artifactId>hamcrest-core</artifactId>
|
||||||
|
<version>1.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.objenesis</groupId>
|
||||||
|
<artifactId>objenesis</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
|
"#).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
tests/sample_project/Jargo.toml
Normal file
10
tests/sample_project/Jargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
group = "nl.sander"
|
||||||
|
name = "sample_project"
|
||||||
|
version = "0.1-SNAPSHOT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[test-dependencies]
|
||||||
|
"junit:junit" = "4.8.2"
|
||||||
|
"org.mockito:mockito-core" = "1.9.5"
|
||||||
8
tests/sample_project/src/main/java/nl/sander/Sample.java
Normal file
8
tests/sample_project/src/main/java/nl/sander/Sample.java
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package nl.sander.sample;
|
||||||
|
|
||||||
|
public class Sample {
|
||||||
|
|
||||||
|
public static int getTheNumber() {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
tests/sample_project/src/test/java/nl/sander/SampleTest.java
Normal file
13
tests/sample_project/src/test/java/nl/sander/SampleTest.java
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package nl.sander.sample;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class SampleTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public int getTheNumberTest() {
|
||||||
|
assertEquals(42, Sample.getTheNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue