diff --git a/.orbit/gverb.py b/.orbit/gverb.py index bee3237..61805f8 100644 --- a/.orbit/gverb.py +++ b/.orbit/gverb.py @@ -133,7 +133,7 @@ .arg("--loop-limit="+str(MAX_TESTS) if MAX_TESTS != None else None) \ .arg("--dut").arg(dut_data) \ .arg("--tb").arg(tb_data) \ - .args(['-g ' + item.to_str() for item in GENERICS]) \ + .args(['-g=' + item.to_str() for item in GENERICS]) \ .arg("python") \ .arg("--") \ .arg(py_model) \ diff --git a/README.md b/README.md index 0113df1..07b6083 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ Any of the components may have one or more implementations; install the componen If you are using Linux or macOS, you can install all the components (using `pip`, `orbit`, and `cargo`): ``` curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cdotrus/verb/trunk/install.sh | bash -s -- - ``` ## Details diff --git a/examples/add/add_tb.py b/examples/add/add_tb.py index 419997d..da41e94 100755 --- a/examples/add/add_tb.py +++ b/examples/add/add_tb.py @@ -43,9 +43,9 @@ def eval(self): self.cout.data = temp[self.width] return self - def force_carry_out(*p: Signal): - in0 = random.randint(1, p[0].max()) - return (in0, p[1].max() + 1 - in0) + def force_carry_out(in0: Signal, in1: Signal): + in0 = random.randint(1, in0.max()) + return (in0, in1.max() + 1 - in0) pass @@ -103,16 +103,16 @@ def force_carry_out(*p: Signal): CoverPoint("in0 and in1 equal 0") \ .goal(1) \ .target(add.in0, add.in1) \ - .def_advance(lambda p: (p[0].min(), p[1].min())) \ - .def_cover(lambda p: int(p[0]) == 0 and int(p[1]) == 0) \ + .def_advance(lambda in0, in1: (in0.min(), in1.min())) \ + .def_cover(lambda in0, in1: int(in0) == 0 and int(in1) == 0) \ .apply() # Check to make sure both inputs are the maximum value at the same time at least once. CoverPoint("in0 and in1 equal max") \ .goal(1) \ .target(add.in0, add.in1) \ - .def_advance(lambda p: (p[0].max(), p[1].max())) \ - .def_cover(lambda p: int(p[0]) == p[0].max() and int(p[1]) == p[1].max()) \ + .def_advance(lambda in0, in1: (in0.max(), in1.max())) \ + .def_cover(lambda in0, in1: int(in0) == in0.max() and int(in1) == in1.max()) \ .apply() # Cover the case that the carry out is generated at least 10 times. diff --git a/src/bin/verb/src/error.rs b/src/bin/verb/src/error.rs index 09889f7..061d3d8 100644 --- a/src/bin/verb/src/error.rs +++ b/src/bin/verb/src/error.rs @@ -1,4 +1,3 @@ - pub type AnyError = Box; type LastError = String; diff --git a/src/bin/verb/src/events/comment.rs b/src/bin/verb/src/events/comment.rs index 6702a4c..4824fb6 100644 --- a/src/bin/verb/src/events/comment.rs +++ b/src/bin/verb/src/events/comment.rs @@ -5,8 +5,6 @@ pub struct Comment { impl From for Comment { fn from(value: String) -> Self { - Self { - inner: value - } + Self { inner: value } } -} \ No newline at end of file +} diff --git a/src/bin/verb/src/events/mod.rs b/src/bin/verb/src/events/mod.rs index 116e08d..e2e04e5 100644 --- a/src/bin/verb/src/events/mod.rs +++ b/src/bin/verb/src/events/mod.rs @@ -1,16 +1,16 @@ use std::{path::PathBuf, str::FromStr}; -use timestamp::Timestamp; +use comment::Comment; use severity::Severity; +use timestamp::Timestamp; use topic::Topic; -use comment::Comment; use crate::error::{AnyError, Error}; -pub mod timestamp; +pub mod comment; pub mod severity; +pub mod timestamp; pub mod topic; -pub mod comment; #[derive(Debug, PartialEq)] pub struct Events { @@ -52,9 +52,7 @@ impl FromStr for Events { while let Some(raw_e) = raw_events.next() { events.push(Event::from_str(raw_e)?); } - Ok(Self { - inner: events, - }) + Ok(Self { inner: events }) } } @@ -108,7 +106,6 @@ impl FromStr for Event { } } - #[cfg(test)] mod tests { use super::*; @@ -116,11 +113,14 @@ mod tests { #[test] fn from_str() { let ev = "180000000fs INFO ASSERT_EQ sum receives 0110 and expects 0110"; - assert_eq!(Event::from_str(ev).unwrap(), Event { - timestamp: Timestamp::with("180000000", "fs"), - severity: Severity::Info, - topic: Topic::from("ASSERT_EQ".to_string()), - comment: Comment::from("sum receives 0110 and expects 0110".to_string()), - }) + assert_eq!( + Event::from_str(ev).unwrap(), + Event { + timestamp: Timestamp::with("180000000", "fs"), + severity: Severity::Info, + topic: Topic::from("ASSERT_EQ".to_string()), + comment: Comment::from("sum receives 0110 and expects 0110".to_string()), + } + ) } -} \ No newline at end of file +} diff --git a/src/bin/verb/src/events/severity.rs b/src/bin/verb/src/events/severity.rs index d437bde..c860b38 100644 --- a/src/bin/verb/src/events/severity.rs +++ b/src/bin/verb/src/events/severity.rs @@ -41,7 +41,7 @@ impl FromStr for Severity { "WARN" => Ok(Self::Warn), "ERROR" => Ok(Self::Error), "FATAL" => Ok(Self::Fatal), - _ => Err(Error::UnknownSeverity(s.to_string())) + _ => Err(Error::UnknownSeverity(s.to_string())), } } -} \ No newline at end of file +} diff --git a/src/bin/verb/src/events/timestamp.rs b/src/bin/verb/src/events/timestamp.rs index d5b7d9e..3188b4a 100644 --- a/src/bin/verb/src/events/timestamp.rs +++ b/src/bin/verb/src/events/timestamp.rs @@ -30,9 +30,8 @@ impl FromStr for Timestamp { time: time.to_string(), units: unit.to_string(), }) - }, - None => return Err(Error::MissingTimeUnits) + } + None => return Err(Error::MissingTimeUnits), } } } - diff --git a/src/bin/verb/src/events/topic.rs b/src/bin/verb/src/events/topic.rs index 95683f9..61d0381 100644 --- a/src/bin/verb/src/events/topic.rs +++ b/src/bin/verb/src/events/topic.rs @@ -5,8 +5,6 @@ pub struct Topic { impl From for Topic { fn from(value: String) -> Self { - Self { - inner: value - } + Self { inner: value } } -} \ No newline at end of file +} diff --git a/src/bin/verb/src/lib.rs b/src/bin/verb/src/lib.rs index 33cf798..80b5cf8 100644 --- a/src/bin/verb/src/lib.rs +++ b/src/bin/verb/src/lib.rs @@ -1,6 +1,6 @@ pub mod error; +pub mod events; pub mod generic; pub mod ops; pub mod unit; pub mod verb; -pub mod events; \ No newline at end of file diff --git a/src/bin/verb/src/ops/check.rs b/src/bin/verb/src/ops/check.rs index 2fb008e..2cb0d78 100644 --- a/src/bin/verb/src/ops/check.rs +++ b/src/bin/verb/src/ops/check.rs @@ -23,7 +23,6 @@ impl Subcommand<()> for Check { } fn execute(self, _c: &()) -> proc::Result { - let events = Events::load(&self.events)?; let mut total_points: Option = None; @@ -34,8 +33,8 @@ impl Subcommand<()> for Check { // find the coverage lines let stats = data.split_terminator('\n'); for s in stats { - let is_total_points = s.starts_with("Total points:"); - let is_points_covered = s.starts_with("Points covered:"); + let is_total_points = s.starts_with("Points:"); + let is_points_covered = s.starts_with("Count:"); if is_total_points == true || is_points_covered == true { let (_name, value) = s.split_once(':').unwrap(); let value = value.trim(); @@ -49,7 +48,11 @@ impl Subcommand<()> for Check { } if self.stats == true { - println!("info: simulation score: {}/{}", events.count_normal(), events.len()); + println!( + "info: simulation score: {}/{}", + events.count_normal(), + events.len() + ); } // check coverage @@ -59,7 +62,7 @@ impl Subcommand<()> for Check { if self.stats == true { println!("info: coverage score: {}/{}", pc, tp); } - match tp == pc { + match pc >= tp { true => (), false => return Err(Error::FailedCoverage(tp - pc))?, } @@ -73,15 +76,15 @@ impl Subcommand<()> for Check { } const HELP: &str = "\ -Analyze the simulation's results. +Analyze the output of a hardware simulation. Usage: verb check [options] Args: - file system path to the events log + path to locate the simulation's events log --stats display summary statistics Options: - --coverage path to read coverage report + --coverage path to locate the model's coverage report "; diff --git a/src/bin/verb/src/ops/link.rs b/src/bin/verb/src/ops/link.rs index f6cd68c..a71e4e6 100644 --- a/src/bin/verb/src/ops/link.rs +++ b/src/bin/verb/src/ops/link.rs @@ -7,12 +7,11 @@ pub struct Link { bfm: bool, send: bool, comp: bool, + // use an 'exclude' list to ignore ports in the bfm exclude: Vec, list: bool, } -// use an 'exclude' list to ignore ports in the bfm - impl Subcommand<()> for Link { fn interpret(cli: &mut Cli) -> cli::Result { cli.help(Help::with(HELP))?; @@ -91,18 +90,18 @@ impl Subcommand<()> for Link { } const HELP: &str = "\ -Generate code snippets for hw/sw synchronization. +Generate code snippets for hw/sw coherency. Usage: verb link [options] Args: - hw unit's interface encoded in json format + hw unit's interface encoded in json format Options: - --bfm print the hw bus functional model interface - --send print the hw function to send inputs to the dut - --comp print the hw function to compare outputs from the dut + --bfm display the hw bus functional model interface + --send display the hw function to send inputs to the dut + --comp display the hw function to compare outputs from the dut --exclude, -x omit specific ports from the code snippets --list list the port order and exit "; diff --git a/src/bin/verb/src/ops/model.rs b/src/bin/verb/src/ops/model.rs index 466e760..3cfa726 100644 --- a/src/bin/verb/src/ops/model.rs +++ b/src/bin/verb/src/ops/model.rs @@ -23,16 +23,16 @@ impl Subcommand<()> for Model { fn interpret(cli: &mut Cli) -> cli::Result { cli.help(Help::with(HELP))?; Ok(Self { - loop_limit: cli.get(Arg::option("loop-limit"))?, - rand_seed: cli.get(Arg::option("seed"))?, - coverage: cli.get(Arg::option("coverage"))?, - dir: cli.get(Arg::option("directory").switch('C'))?, + loop_limit: cli.get(Arg::option("loop-limit").value("num"))?, + rand_seed: cli.get(Arg::option("seed").value("num"))?, + coverage: cli.get(Arg::option("coverage").value("file"))?, + dir: cli.get(Arg::option("directory").switch('C').value("dir"))?, generics: cli - .get_all(Arg::option("generic").switch('g'))? + .get_all(Arg::option("generic").switch('g').value("key=value"))? .unwrap_or_default(), - dut: cli.require(Arg::option("dut"))?, - tb: cli.require(Arg::option("tb"))?, - model: cli.require(Arg::positional("model"))?, + dut: cli.require(Arg::option("dut").value("json"))?, + tb: cli.require(Arg::option("tb").value("json"))?, + model: cli.require(Arg::positional("command"))?, model_args: cli.remainder()?, }) } @@ -103,21 +103,21 @@ impl Subcommand<()> for Model { } const HELP: &str = "\ -Run the software script for a design's model. +Run a design's software model. Usage: verb model [options] --dut --tb [--] [args]... Args: - file system path used to execute the model - --dut design-under-test's interface encoded in json format - --tb testbench's interface encoded in json format + the command to execute the model + --dut hw design-under-test's interface encoded in json format + --tb hw testbench's interface encoded in json format Options: --generic, -g override a testbench generic - --seed set the random number generator seed - --coverage path to write coverage report - --loop-limit set the maximum number of iterations when using CDTG - --directory, -C change the working directory where the model will run - args arguments to pass to the model's command + --seed the randomness seed + --coverage destination for the coverage report + --loop-limit the max number of main loop iterations + --directory, -C the directory where the model will run + args arguments to pass to the model's command "; diff --git a/src/bin/verb/src/verb.rs b/src/bin/verb/src/verb.rs index 242454d..aa000fd 100644 --- a/src/bin/verb/src/verb.rs +++ b/src/bin/verb/src/verb.rs @@ -72,9 +72,9 @@ Usage: Commands: link generate code snippets for hw/sw coherency - model run the software script for a design's model - check analyze the output from a hardware simulation - + model run a design's software model + check analyze the output of a hardware simulation + Options: --version print the version information and exit --help, -h print this help information and exit diff --git a/src/lib/python/verb/coverage.py b/src/lib/python/verb/coverage.py index 822c558..9a85735 100644 --- a/src/lib/python/verb/coverage.py +++ b/src/lib/python/verb/coverage.py @@ -12,14 +12,21 @@ class Status(_Enum): PASSED = 0 SKIPPED = 1 FAILED = 2 - pass + def to_json(self): + if self == Status.PASSED: + return True + elif self == Status.FAILED: + return False + elif self == Status.SKIPPED: + return None + pass class Coverage: _total_coverages = 0 _passed_coverages = 0 - _goals_met = 0 + _point_count = 0 _total_points = 0 _coverage_report = 'coverage.txt' @@ -75,14 +82,14 @@ def tally_score(): ''' Coverage._total_coverages = 0 Coverage._passed_coverages = 0 - Coverage._goals_met = 0 + Coverage._point_count = 0 Coverage._total_points = 0 net: CoverageNet for net in CoverageNet._group: if net.status() == Status.SKIPPED: continue Coverage._total_coverages += 1 - Coverage._goals_met += net.get_points_met() + Coverage._point_count += net.get_points_met() if type(net) == CoverPoint: Coverage._total_points += 1 else: @@ -103,7 +110,7 @@ def percent() -> float: from 0.00 to 100.00 percent, with rounding to 2 decimal places. ''' Coverage.tally_score() - passed = Coverage._goals_met + passed = Coverage._point_count total = Coverage._total_points return round((passed/total) * 100.0, 2) if total > 0 else None @@ -117,12 +124,17 @@ def save() -> str: path = Coverage._coverage_report Coverage.tally_score() - header = '' + # write to .json + Coverage.to_json() + # write to .txt + header = 'File: Coverage Report' + '\n' header += "Seed: " + str(context.Context.current()._context._seed) + '\n' header += "Iterations: " + str(Coverage.count()) + '\n' - header += "Points covered: " + str(Coverage._goals_met) + '\n' - header += "Total points: " + str(Coverage._total_points) + '\n' - header += "Coverage: " + str(Coverage.percent()) + ' %\n' + header += "Score: " + str(Coverage.percent()) + '\n' + header += "Met: " + ('None' if Coverage._total_points == 0 else str(Coverage._point_count >= Coverage._total_points)) + '\n' + header += "Count: " + str(Coverage._point_count) + '\n' + header += "Points: " + str(Coverage._total_points) + '\n' + with open(path, 'w') as f: # header f.write(header) @@ -137,6 +149,38 @@ def save() -> str: pass + @staticmethod + def get_overall_status() -> Status: + if Coverage._total_points == 0: + return Status.SKIPPED + elif Coverage._point_count >= Coverage._total_points: + return Status.PASSED + else: + return Status.FAILED + + @staticmethod + def to_json() -> str: + ''' + Writes the coverage report as a json encoded string. + ''' + import json + from . import context + + net: CoverageNet + report = { + 'seed': context.Context.current()._context._seed, + 'iterations': int(Coverage.count()), + 'score': Coverage.percent(), + 'met': Coverage.get_overall_status().to_json(), + 'count': int(Coverage._point_count), + 'points': int(Coverage._total_points), + 'nets': [net.to_json() for net in CoverageNet._group] + } + + with open('coverage.json', 'w') as f: + json.dump(report, f, indent=4) + pass + def met(timeout: int=-1) -> bool: ''' @@ -184,7 +228,7 @@ def report_score() -> str: Formats the score as a `str`. ''' Coverage.tally_score() - return (str(Coverage.percent()) + ' % ' if Coverage.percent() != None else 'N/A ') + '(' + str(Coverage._goals_met) + '/' + str(Coverage._total_points) + ' goals)' + return (str(Coverage.percent()) + ' % ' if Coverage.percent() != None else 'N/A ') + '(' + str(Coverage._point_count) + '/' + str(Coverage._total_points) + ' goals)' def check(threshold: float=1.0) -> bool: @@ -195,7 +239,7 @@ def check(threshold: float=1.0) -> bool: - `threshold` expects a floating point value [0, 1.0] ''' Coverage.tally_score() - passed = Coverage._goals_met + passed = Coverage._point_count total = Coverage._total_points if total <= 0: return True @@ -213,6 +257,38 @@ class CoverageNet(_ABC): _group = [] _counter = 0 + def get_type(self) -> str: + if type(self) == CoverCross: + return 'cross' + elif type(self) == CoverGroup: + return 'group' + elif type(self) == CoverRange: + return 'range' + elif type(self) == CoverPoint: + return 'point' + else: + return 'net' + + def to_json(self) -> dict: + ''' + Formats the coverage net into json-friendly data structure. + ''' + data = { + 'name': self._name, + 'type': self.get_type(), + 'met': None if self._bypass == True else self.passed() + } + data.update(self.to_json_internal()) + return data + + @_abstractmethod + def to_json_internal(self) -> dict: + ''' + Returns the particular coverage net into json-friendly data structure with + specifics of that net. + ''' + pass + def bypass(self, bypass: bool): ''' Skips this net when trying to meet coverage if `bypass` is true. @@ -245,7 +321,6 @@ def source(self, *source: Signal): self._source = tuple(source) return self - def sink(self, *sink: Signal): ''' The signal(s) involved in advancing the net's coverage. @@ -359,6 +434,20 @@ def get_partition_count(self) -> int: ''' pass + @_abstractmethod + def get_total_goal_count(self) -> int: + ''' + Returns the number of total goals that must be reached by this net. + ''' + pass + + @_abstractmethod + def get_total_points_met(self) -> int: + ''' + Returns the number of total points collected by this net. + ''' + pass + @_abstractmethod def is_in_sample_space(self, item) -> bool: ''' @@ -464,6 +553,16 @@ class CoverPoint(CoverageNet): ''' from .model import Signal + def to_json_internal(self) -> dict: + data = { + 'count': int(self.get_total_points_met()), + 'goal': int(self.get_total_goal_count()), + } + return data + + def get_total_goal_count(self) -> int: + return self._goal + def goal(self, goal: int): ''' Sets the coverage goal for this net. @@ -471,7 +570,6 @@ def goal(self, goal: int): self._goal = goal return self - def def_advance(self, fn): ''' Set the function or lambda expression that provides vlues to write to the source to advance coverage for this particular goal. @@ -479,14 +577,12 @@ def def_advance(self, fn): self._fn_advance = fn return self - def def_cover(self, fn): ''' Sets the function or lambda expression that provides a way to read values from the sink to check coverage. ''' self._fn_cover = fn return self - def __init__(self, name: str): ''' @@ -502,7 +598,13 @@ def __init__(self, name: str): pass def _transform(self, item): - return item if self._fn_cover == None else self._fn_cover(item) + # unpack the list if one was given + if self._fn_cover == None: + return item + if isinstance(item, (list, tuple)) == True: + return self._fn_cover(*item) + else: + return self._fn_cover(item) def is_in_sample_space(self, item) -> bool: mapped_item = int(self._transform(item)) @@ -524,6 +626,9 @@ def get_points_met(self) -> int: Returns the number of points that have met their goal. ''' return 1 if self._count >= self._goal else 0 + + def get_total_points_met(self) -> int: + return self._count def cover(self, item): ''' @@ -553,6 +658,45 @@ class CoverGroup(CoverageNet): from typing import List as _List from .model import Signal + def to_json_internal(self) -> dict: + bins_reached = 0 + # compute the number of bins that reached their goal + for c in self._macro_bins_count: + if c >= self._goal: + bins_reached += 1 + pass + data = { + 'count': self.get_points_met(), + 'goal': len(self._macro_bins_count), + } + + bins = [] + for i, macro in enumerate(self._macro_bins): + cur_bin = { + 'name': self._macro_to_string(i), + 'met': None if self._bypass == True else int(self._macro_bins_count[i]) >= int(self._goal), + 'count': int(self._macro_bins_count[i]), + 'goal': int(self._goal), + } + hits = [] + for hit in macro: + if hit in self._item_counts.keys(): + hit = { + 'value': str(hit), + 'count': int(self._item_counts[hit]) + } + hits += [hit] + pass + cur_bin['hits'] = hits + bins += [cur_bin] + pass + + data['bins'] = bins + return data + + def get_total_goal_count(self) -> int: + return self._goal * len(self._macro_bins_count) + def goal(self, goal: int): ''' Sets the coverage goal for this net. @@ -567,7 +711,6 @@ def def_advance(self, fn): self._fn_advance = fn return self - def def_cover(self, fn): ''' Sets the function or lambda expression that provides a way to read values from the sink to check coverage. @@ -575,7 +718,6 @@ def def_cover(self, fn): self._fn_cover = fn return self - def max_bins(self, limit: int): ''' Sets the maximum number of bins. @@ -626,6 +768,9 @@ def __init__(self, name: str): # store a hash to the index in the set of bins list self._bins_lookup = dict() + # store the counts of individual items + self._item_counts = dict() + # defining a bin range is more flexible for defining a large space # store the actual values when mapped items cover toward the goal @@ -695,8 +840,19 @@ def cover(self, item): # increment the count of this item being detected self._mapped_items[i_macro][mapped_item] += 1 pass + # track individual count for this item + if mapped_item not in self._item_counts.keys(): + self._item_counts[mapped_item] = 0 + self._item_counts[mapped_item] += 1 + return is_progress + def get_total_points_met(self) -> int: + points_met = 0 + for count in self._macro_bins_count: + points_met += count + return points_met + def get_points_met(self) -> int: points_met = 0 for count in self._macro_bins_count: @@ -781,7 +937,7 @@ def to_string(self, verbose: bool=False) -> str: longest_len = _find_longest_str_len([self._macro_to_string(i) for i, _ in enumerate(self._macro_bins)]) is_first = True # print the coverage analysis - for i, group in enumerate(self._macro_bins): + for i, _ in enumerate(self._macro_bins): if is_first == False: result += '\n ' phrase = str(self._macro_to_string(i)) @@ -793,12 +949,8 @@ def to_string(self, verbose: bool=False) -> str: sub_longest_len = _find_longest_str_len(self._mapped_items[i].keys()) seq = [(key, val) for key, val in self._mapped_items[i].items()] seq.sort() - LIMITER = 20 for j, (key, val) in enumerate(seq): result += '\n ' - if j > LIMITER: - result += '...' - break result += str(key) + ': ' + (' ' * (sub_longest_len - len(str(key)))) + str(val) pass @@ -823,6 +975,54 @@ class CoverRange(CoverageNet): ''' from .model import Signal + def to_json_internal(self) -> dict: + data = { + 'count': int(self.get_points_met()), + 'goal': int(self.get_total_goal_count()), + } + + bins = [] + + # print the coverage analysis + for i, _ in enumerate(self._table): + # collect a single bin + if self._step_size > 1: + step = str(i * self._step_size) + '..=' + str(((i+1) * self._step_size)-1) + else: + step = i + pass + count = int(self._table_counts[i]) + + cur_bin = { + 'name': str(step), + 'met': None if self._bypass == True else count >= self._goal, + 'count': int(count), + 'goal': int(self._goal), + } + # get each hit that helped toward the current bin's goal + hits = [] + if self._step_size > 1 and i in self._mapped_items.keys(): + seq = [(key, val) for key, val in self._mapped_items[i].items()] + seq.sort() + for (key, val) in seq: + cur_hit = { + 'value': str(key), + 'count': int(val) + } + hits += [cur_hit] + pass + # update the current bin's account for its hits + cur_bin['hits'] = hits + # add to the bin list + bins += [cur_bin] + pass + + data['bins'] = bins + return data + + def get_total_goal_count(self) -> int: + return self._goal * int(len(self._table_counts)) + def goal(self, goal: int): ''' Sets the coverage goal for this net. @@ -914,6 +1114,12 @@ def get_points_met(self) -> int: if entry >= self._goal: points_met += 1 return points_met + + def get_total_points_met(self) -> int: + points_met = 0 + for entry in self._table_counts: + points_met += entry + return points_met def passed(self) -> bool: ''' @@ -959,11 +1165,11 @@ def cover(self, item) -> bool: # track original items that count toward their space of the domain if index not in self._mapped_items.keys(): self._mapped_items[index] = dict() - if item not in self._mapped_items[index].keys(): + # store the count of the integer value of the number for encounters tracking/stats + if mapped_item not in self._mapped_items[index].keys(): self._mapped_items[index][mapped_item] = 0 # increment the count of this item being detected self._mapped_items[index][mapped_item] += 1 - return is_progress def advance(self, rand: bool=False): @@ -1013,7 +1219,7 @@ def to_string(self, verbose: bool) -> str: longest_len = len(str(self._stop-1)) is_first = True # print the coverage analysis - for i, _group in enumerate(self._table): + for i, _ in enumerate(self._table): if is_first == False: result += '\n ' if self._step_size > 1: @@ -1027,12 +1233,8 @@ def to_string(self, verbose: bool) -> str: sub_longest_len = _find_longest_str_len(self._mapped_items[i].keys()) seq = [(key, val) for key, val in self._mapped_items[i].items()] seq.sort() - LIMITER = 20 for i, (key, val) in enumerate(seq): result += '\n ' - if i > LIMITER: - result += '...' - break result += str(key) + ': ' + (' ' * (sub_longest_len - len(str(key)))) + str(val) pass is_first = False @@ -1057,6 +1259,13 @@ class CoverCross(CoverageNet): ''' from typing import List as List + def to_json_internal(self) -> dict: + data = self._inner.to_json_internal() + return data + + def get_total_goal_count(self) -> int: + return self._inner.get_total_goal_count() + def nets(self, *net: CoverageNet): ''' Specifies the coverage nets to cross. @@ -1240,6 +1449,9 @@ def get_points_met(self) -> int: Returns the number of points that have met their goal. ''' return self._inner.get_points_met() + + def get_total_points_met(self) -> int: + return self._inner.get_total_points_met() def cover(self, item): if self.is_in_sample_space(item) == False: