Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data loading #16

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,002 changes: 1,000 additions & 2 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ edition = "2021"
[workspace.dependencies]
anyhow = "1.0.86"
camino = "1.1.7"
clap = { version = "4.5.13", features = ["derive"] }
clap = { version = "4.5.13", features = ["derive"] }
cairo-lang-sierra = "2.7.0"
serde = "1.0.204"
serde_json = "1.0.122"
trace-data = { git = "https://github.com/software-mansion/cairo-profiler/", tag = "v0.5.0" }
4 changes: 4 additions & 0 deletions crates/cairo-coverage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ edition.workspace = true
[dependencies]
anyhow.workspace = true
camino.workspace = true
cairo-lang-sierra.workspace = true
clap.workspace = true
serde.workspace = true
serde_json.workspace = true
trace-data.workspace = true
26 changes: 26 additions & 0 deletions crates/cairo-coverage/src/data_loader/deserialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use anyhow::{Context, Result};
use cairo_lang_sierra::debug_info::Annotations;
use camino::Utf8PathBuf;
use serde::de::DeserializeOwned;
use std::fs;

pub fn from_file<T: DeserializeOwned>(file_path: &Utf8PathBuf) -> Result<T> {
fs::read_to_string(file_path)
.context(format!("Failed to read file at path: {file_path}"))
.and_then(|content| {
serde_json::from_str(&content).context(format!(
"Failed to deserialize JSON content from file at path: {file_path}"
))
})
}

pub fn by_namespace<T: DeserializeOwned>(annotations: &Annotations, namespace: &str) -> Result<T> {
annotations
.get(namespace)
.cloned()
.context(format!("Expected key: {namespace} but was missing"))
.and_then(|value| {
serde_json::from_value(value)
.context(format!("Failed to deserialize at key: {namespace}"))
})
}
2 changes: 2 additions & 0 deletions crates/cairo-coverage/src/data_loader/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod deserialize;
pub mod types;
98 changes: 98 additions & 0 deletions crates/cairo-coverage/src/data_loader/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::data_loader::deserialize;
use anyhow::{ensure, Result};
use cairo_lang_sierra::debug_info::Annotations;
use cairo_lang_sierra::program::StatementIdx;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::ops::Deref;

const PROFILER_NAMESPACE: &str = "github.com/software-mansion/cairo-profiler";
const COVERAGE_NAMESPACE: &str = "github.com/software-mansion/cairo-coverage";

type FileLocation = String;
type FunctionName = String;

type CodeLocation = (FileLocation, Range);

#[repr(transparent)]
pub struct StatementMap(HashMap<StatementIdx, StatementOrigin>);

impl Deref for StatementMap {
type Target = HashMap<StatementIdx, StatementOrigin>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct Range {
pub start: Position,
pub end: Position,
}

#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct Position {
pub line: usize,
}

#[derive(Deserialize)]
struct CoverageAnnotations {
statements_code_locations: HashMap<StatementIdx, Vec<CodeLocation>>,
}

#[derive(Deserialize)]
struct ProfilerAnnotations {
statements_functions: HashMap<StatementIdx, Vec<FunctionName>>,
}

#[allow(dead_code)] // Temporary
#[derive(Debug)]
pub struct StatementOrigin {
pub code_locations: Vec<CodeLocation>,
pub function_names: Vec<FunctionName>,
}

impl TryFrom<&Annotations> for StatementMap {
type Error = anyhow::Error;

fn try_from(annotations: &Annotations) -> Result<Self> {
let CoverageAnnotations {
statements_code_locations,
} = deserialize::by_namespace(annotations, COVERAGE_NAMESPACE)?;

let ProfilerAnnotations {
statements_functions,
} = deserialize::by_namespace(annotations, PROFILER_NAMESPACE)?;

ensure!(
have_same_keys(&statements_code_locations, &statements_functions),
"{COVERAGE_NAMESPACE} and {PROFILER_NAMESPACE} doesn't have the same statement idx"
);

let statement_map = statements_code_locations
.into_iter()
.map(|(key, code_locations)| {
// Safe to unwrap because we ensured that the keys are the same
let function_names = statements_functions.get(&key).unwrap().to_owned();
(
key,
StatementOrigin {
code_locations,
function_names,
},
)
})
.collect();

Ok(Self(statement_map))
}
}

fn have_same_keys<K, V1, V2>(map1: &HashMap<K, V1>, map2: &HashMap<K, V2>) -> bool
where
K: Eq + Hash,
{
map1.keys().collect::<HashSet<&K>>() == map2.keys().collect::<HashSet<&K>>()
}
65 changes: 65 additions & 0 deletions crates/cairo-coverage/src/input_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::data_loader::deserialize;
use crate::data_loader::types::StatementMap;
use anyhow::{Context, Result};
use cairo_lang_sierra::program::{Program, VersionedProgram};
use camino::Utf8PathBuf;
use trace_data::CallTrace;

#[allow(dead_code)] // Temporary
struct InputData {
call_trace: CallTrace,
program: Program,
statement_map: StatementMap,
}

impl InputData {
#[allow(dead_code)] // Temporary
pub fn new(
call_trace_path: &Utf8PathBuf,
versioned_program_path: &Utf8PathBuf,
) -> Result<Self> {
let VersionedProgram::V1 { program, .. } = deserialize::from_file(versioned_program_path)?;
let annotations = &program
.debug_info
.context(format!("Debug info not found in: {versioned_program_path}"))?
.annotations;

Ok(Self {
call_trace: deserialize::from_file(call_trace_path)?,
program: program.program,
statement_map: annotations.try_into()?,
})
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::data_loader::types::{Position, Range};
use cairo_lang_sierra::program::StatementIdx;

const TRACE: &str = "tests/data/config/trace.json";
const VERSIONED_PROGRAM: &str = "tests/data/config/versioned_program.json";

#[test]
fn versioned_program() {
let config = InputData::new(&TRACE.into(), &VERSIONED_PROGRAM.into()).unwrap();
assert_eq!(config.statement_map.len(), 142);

let (file_location, range) = &config.statement_map[&StatementIdx(1)].code_locations[0];
assert!(file_location.ends_with("test_call.cairo"));

let statement_details = &config.statement_map[&StatementIdx(1)];
assert_eq!(
statement_details.function_names,
&["tests::test_call::my_test"]
);
assert_eq!(
range,
&Range {
start: Position { line: 5 },
end: Position { line: 5 },
}
);
}
}
2 changes: 2 additions & 0 deletions crates/cairo-coverage/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
mod cli;
mod data_loader;
mod input_data;

use anyhow::{Context, Result};
use clap::Parser;
Expand Down
Loading
Loading