183 lines
4.9 KiB
Go
183 lines
4.9 KiB
Go
package encode
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"math"
|
||
|
|
"os"
|
||
|
|
"os/exec"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/netsynth/netsynth/classify"
|
||
|
|
"github.com/netsynth/netsynth/synth"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TestMP3Valid synthesizes from 3 synthetic WindowSnapshots and validates the
|
||
|
|
// output file is a valid MP3 using ffprobe.
|
||
|
|
func TestMP3Valid(t *testing.T) {
|
||
|
|
// Check ffprobe is available
|
||
|
|
if _, err := exec.LookPath("ffprobe"); err != nil {
|
||
|
|
t.Skip("ffprobe not found in PATH; skipping MP3 validation test")
|
||
|
|
}
|
||
|
|
|
||
|
|
snaps := []classify.WindowSnapshot{
|
||
|
|
{
|
||
|
|
Counts: map[classify.TrafficClass]int64{
|
||
|
|
classify.ClassICMP: 50,
|
||
|
|
classify.ClassDNS: 30,
|
||
|
|
},
|
||
|
|
TotalPackets: 80,
|
||
|
|
WindowIndex: 0,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Counts: map[classify.TrafficClass]int64{
|
||
|
|
classify.ClassHTTPS: 200,
|
||
|
|
},
|
||
|
|
TotalPackets: 200,
|
||
|
|
WindowIndex: 1,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Counts: map[classify.TrafficClass]int64{
|
||
|
|
classify.ClassSSH: 10,
|
||
|
|
classify.ClassHTTP: 80,
|
||
|
|
},
|
||
|
|
TotalPackets: 90,
|
||
|
|
WindowIndex: 2,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
tmpFile, err := os.CreateTemp("", "netsynth-test-*.mp3")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("create temp file: %v", err)
|
||
|
|
}
|
||
|
|
tmpPath := tmpFile.Name()
|
||
|
|
tmpFile.Close()
|
||
|
|
defer os.Remove(tmpPath)
|
||
|
|
|
||
|
|
if err := RunSynthesis(snaps, tmpPath); err != nil {
|
||
|
|
t.Fatalf("RunSynthesis: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify file exists and is non-empty
|
||
|
|
info, err := os.Stat(tmpPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("stat output file: %v", err)
|
||
|
|
}
|
||
|
|
if info.Size() == 0 {
|
||
|
|
t.Fatal("output file is empty")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate with ffprobe
|
||
|
|
out, err := exec.Command("ffprobe",
|
||
|
|
"-v", "error",
|
||
|
|
"-show_entries", "format=format_name,duration,nb_streams",
|
||
|
|
"-of", "csv=p=0",
|
||
|
|
tmpPath,
|
||
|
|
).CombinedOutput()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ffprobe error: %v\noutput: %s", err, out)
|
||
|
|
}
|
||
|
|
|
||
|
|
outStr := string(out)
|
||
|
|
if !strings.Contains(outStr, "mp3") {
|
||
|
|
t.Errorf("expected format_name to contain 'mp3', got: %q", outStr)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ffprobe csv=p=0 format: format_name,duration,nb_streams
|
||
|
|
// Duration should be > 0; nb_streams should be 1
|
||
|
|
// We check that the output is non-empty and contains expected fields
|
||
|
|
fields := strings.Split(strings.TrimSpace(outStr), ",")
|
||
|
|
if len(fields) < 3 {
|
||
|
|
t.Errorf("unexpected ffprobe output format: %q", outStr)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestZeroPacketError verifies that RunSynthesis returns an error containing
|
||
|
|
// "no packets" and does NOT create an output file when given zero-packet input.
|
||
|
|
func TestZeroPacketError(t *testing.T) {
|
||
|
|
// Test 1: empty snapshot slice
|
||
|
|
tmpPath := "/tmp/netsynth-should-not-exist-" + t.Name() + ".mp3"
|
||
|
|
defer os.Remove(tmpPath)
|
||
|
|
|
||
|
|
err := RunSynthesis([]classify.WindowSnapshot{}, tmpPath)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected error for empty snapshot slice, got nil")
|
||
|
|
}
|
||
|
|
if !strings.Contains(err.Error(), "no packets") {
|
||
|
|
t.Errorf("expected error to contain 'no packets', got: %q", err.Error())
|
||
|
|
}
|
||
|
|
if _, statErr := os.Stat(tmpPath); !os.IsNotExist(statErr) {
|
||
|
|
t.Error("output file should NOT exist after zero-packet error")
|
||
|
|
os.Remove(tmpPath)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Test 2: snapshots where all TotalPackets == 0
|
||
|
|
tmpPath2 := "/tmp/netsynth-should-not-exist-zero-" + t.Name() + ".mp3"
|
||
|
|
defer os.Remove(tmpPath2)
|
||
|
|
|
||
|
|
zeroSnaps := []classify.WindowSnapshot{
|
||
|
|
{Counts: map[classify.TrafficClass]int64{}, TotalPackets: 0, WindowIndex: 0},
|
||
|
|
{Counts: map[classify.TrafficClass]int64{}, TotalPackets: 0, WindowIndex: 1},
|
||
|
|
}
|
||
|
|
err2 := RunSynthesis(zeroSnaps, tmpPath2)
|
||
|
|
if err2 == nil {
|
||
|
|
t.Fatal("expected error for zero-packet snapshots, got nil")
|
||
|
|
}
|
||
|
|
if !strings.Contains(err2.Error(), "no packets") {
|
||
|
|
t.Errorf("expected error to contain 'no packets', got: %q", err2.Error())
|
||
|
|
}
|
||
|
|
if _, statErr := os.Stat(tmpPath2); !os.IsNotExist(statErr) {
|
||
|
|
t.Error("output file should NOT exist after zero-packet error")
|
||
|
|
os.Remove(tmpPath2)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestEncodeMP3DirectBytes encodes a 440 Hz sine wave directly via EncodeMP3
|
||
|
|
// and validates the output as a valid MP3 file.
|
||
|
|
func TestEncodeMP3DirectBytes(t *testing.T) {
|
||
|
|
if _, err := exec.LookPath("ffprobe"); err != nil {
|
||
|
|
t.Skip("ffprobe not found in PATH; skipping MP3 validation test")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate 1 second of 440 Hz stereo sine wave
|
||
|
|
sampleRate := synth.SampleRate
|
||
|
|
numFrames := sampleRate
|
||
|
|
frames := make([][2]float64, numFrames)
|
||
|
|
for i := range frames {
|
||
|
|
v := 0.5 * math.Sin(2.0*math.Pi*440.0*float64(i)/float64(sampleRate))
|
||
|
|
frames[i] = [2]float64{v, v}
|
||
|
|
}
|
||
|
|
|
||
|
|
tmpFile, err := os.CreateTemp("", "netsynth-sine-test-*.mp3")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("create temp file: %v", err)
|
||
|
|
}
|
||
|
|
tmpPath := tmpFile.Name()
|
||
|
|
tmpFile.Close()
|
||
|
|
defer os.Remove(tmpPath)
|
||
|
|
|
||
|
|
if err := EncodeMP3(tmpPath, frames, sampleRate); err != nil {
|
||
|
|
t.Fatalf("EncodeMP3: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
info, err := os.Stat(tmpPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("stat output file: %v", err)
|
||
|
|
}
|
||
|
|
if info.Size() == 0 {
|
||
|
|
t.Fatal("output file is empty")
|
||
|
|
}
|
||
|
|
|
||
|
|
out, err := exec.Command("ffprobe",
|
||
|
|
"-v", "error",
|
||
|
|
"-show_entries", "format=format_name",
|
||
|
|
"-of", "csv=p=0",
|
||
|
|
tmpPath,
|
||
|
|
).CombinedOutput()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ffprobe error: %v\noutput: %s", err, out)
|
||
|
|
}
|
||
|
|
if !strings.Contains(string(out), "mp3") {
|
||
|
|
t.Errorf("expected MP3 format, got: %q", string(out))
|
||
|
|
}
|
||
|
|
}
|