Algorithm-Classifier-IsolationForest

 view release on metacpan or  search on metacpan

t/92-streamc.t  view on Meta::CPAN

}
{
	open my $fh, '>', $bad_csv or die $!;
	print {$fh} "0.1,0.2\n0.3,0.4\n0.5,0.6,0.7\n";
	close $fh;
}

subtest 'command mode: ping, stats, --json, exit codes' => sub {
	my $out = `$sc --ping 2>&1`;
	is( $?, 0, '--ping exits 0' ) or diag $out;
	like( $out, qr/\Apong\n\z/, 'and says pong' );

	$out = `$sc --ping --json 2>&1`;
	my $obj = eval { $JSON->decode($out) };
	ok( !$@, '--json output parses' ) or diag $out;
	is( $obj->{ok}, 'pong', 'raw reply carried through' );

	$out = `$sc --stats 2>&1`;
	is( $?, 0, '--stats exits 0' );
	like( $out, qr/^\s+set\s+alpha$/m, 'stats renders the set' );
	like( $out, qr/^\s+seen\s+0$/m,    'nothing learned yet' );

	$out = `$sc --ping --stats 2>&1`;
	isnt( $?, 0, 'two command flags are refused' );
	$out = `$sc 2>&1`;
	isnt( $?, 0, 'no mode at all is refused' );
	$out = `$sc --relearn-threshold 2>&1`;
	isnt( $?, 0, 'relearn-threshold without contamination propagates the daemon error' );
	like( $out, qr/contamination/, 'and names the cause' );
}; ## end 'command mode: ping, stats, --json, exit codes' => sub

subtest 'CSV stream mode: learn, prequential, score, -d' => sub {
	my $out = `$sc -i $warm_csv --mode learn 2>&1`;
	is( $?,   0,  '--mode learn exits 0' ) or diag $out;
	is( $out, '', 'and emits nothing' );
	like( `$sc --stats`, qr/^\s+seen\s+60$/m, 'the daemon learned all 60 rows' );

	$out = `$sc -i $q_csv 2>&1`;
	is( $?, 0, 'prequential stream exits 0' );
	my @lines = split /\n/, $out;
	is( scalar @lines, 5, 'one output row per input row' );
	like( $lines[0],     qr/\A[\d.eE+-]+,[01]\z/, 'rows match "score,label"' );
	like( `$sc --stats`, qr/^\s+seen\s+65$/m,     'prequential rows were learned' );

	$out = `$sc -i $q_csv --mode score -d 2>&1`;
	is( $?, 0, 'score mode with -d exits 0' );
	my @f = split /,/, ( split /\n/, $out )[0];
	is( scalar @f, 4, '-d output has 2 features + score + label' );
	like( `$sc --stats`, qr/^\s+seen\s+65$/m, 'score mode did not advance the model' );

	# --batch 2 over 5 rows exercises partial-batch flush and ordering.
	my $batched = `$sc -i $q_csv --mode score --batch 2 2>&1`;
	my $whole   = `$sc -i $q_csv --mode score 2>&1`;
	is( $batched, $whole, 'batch size does not change the output' );

	# -o writes the same thing to a file.
	my $out_csv = "$tmp/scores.csv";
	`$sc -i $q_csv --mode score -o $out_csv 2>&1`;
	is(
		scalar(
			do { local ( @ARGV, $/ ) = ($out_csv); <> }
		),
		$whole,
		'-o file matches stdout output'
	);
}; ## end 'CSV stream mode: learn, prequential, score, -d' => sub

subtest 'jsonl stream mode: tagged rows, verbatim replies' => sub {
	my $jsonl = "$tmp/rows.jsonl";
	{
		open my $fh, '>', $jsonl or die $!;
		print {$fh} '{"cpu":0.5,"mem":0.5}' . "\n" . '{"cpu":9,"mem":9}' . "\n";
		close $fh;
	}
	my $out = `$sc -i $jsonl --jsonl --batch 1 --mode score 2>&1`;
	is( $?, 0, 'jsonl stream exits 0' ) or diag $out;
	my @lines = split /\n/, $out;
	is( scalar @lines, 2, 'one reply line per row at --batch 1' );
	my $r = $JSON->decode( $lines[1] );
	like( $r->{scores}[0][0], qr/\A[\d.eE+-]+\z/, 'replies are the daemon JSON, verbatim' );

	$out = `$sc -i $jsonl --jsonl -d 2>&1`;
	isnt( $?, 0, '-d with --jsonl is refused' );
}; ## end 'jsonl stream mode: tagged rows, verbatim replies' => sub

subtest 'errors are attributed to the input line' => sub {
	my $out = `$sc -i $bad_csv --mode score 2>&1`;
	isnt( $?, 0, 'a ragged CSV exits non-zero' );
	like( $out, qr/Line 3 of input has 3 columns but expected 2/, 'client-side check names line 3' );

	my $jsonl = "$tmp/badrows.jsonl";
	{
		open my $fh, '>', $jsonl or die $!;
		print {$fh} '{"cpu":1,"mem":2}' . "\n" . '{"cpu":1}' . "\n";
		close $fh;
	}
	$out = `$sc -i $jsonl --jsonl --mode score 2>&1`;
	isnt( $?, 0, 'a daemon-rejected row exits non-zero' );
	like( $out, qr/line 2 of input: missing feature name/, 'daemon error mapped back to input line 2' );

	like( `$sc --ping`, qr/pong/, 'the daemon survived all of it' );
}; ## end 'errors are attributed to the input line' => sub

subtest 'connect failure and timeouts' => sub {
	my $out = `$^X -Ilib $bin streamc --socket $tmp/nope.sock --ping 2>&1`;
	isnt( $?, 0, 'missing socket exits non-zero' );
	like( $out, qr/is streamd running/, 'error hints at the cause' );

	$out = `$^X -Ilib $bin streamc --set 'bad/name' --ping 2>&1`;
	isnt( $?, 0, 'invalid set name is refused' );
	like( $out, qr/--set/, 'and names the flag' );
};

subtest 'save through the client' => sub {
	my $out = `$sc --save 2>&1`;
	is( $?, 0, '--save exits 0' );
	like( $out, qr/^\s+saved\s+(oiforest-\S+\.json)$/m, 'prints the saved file name' );
	my ($name) = $out =~ /saved\s+(\S+)/;
	ok( -f "$mdir/alpha/$name", 'the file exists under the set model dir' );
};



( run in 0.588 second using v1.01-cache-2.11-cpan-a9496e3eb41 )