| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- package faultRecordFile
- import (
- "archive/zip"
- "bytes"
- "os"
- "strings"
- "testing"
- proto "fwh-toolbox-server/function/FaultRecordProtocol"
- )
- const (
- sampleArchive = "../../../docs/fault-record/2026-10-14_05_46_20.tar.bz2"
- // 2026-10-14 样本 379(cmd=0, code=3) 的 hexData
- sampleDirectoryHex = "0F0107E801010000220000000110020207EA070E0235230000000110010307EA080C0203050000000110010407EA080D1528280000000110010507EA080E0318100000000110010607EA0810033A050000000110010707EA08120303080000000110010807E90B18010B2E0000000110010907E90B1A152C060000000110010A07E705041601030000000110020B07E705041602210000000110020C07E705041603280000000110020D07E7050416042E0000000110020E07E70504161C0E0000000110020F07EA061D0F0723000000011001"
- )
- func TestParseDirectory(t *testing.T) {
- v, err := ParseRealtime(3, sampleDirectoryHex)
- if err != nil {
- t.Fatal(err)
- }
- list := v.([]DirectoryEntry)
- if len(list) != 15 {
- t.Fatalf("entries = %d", len(list))
- }
- e := list[6]
- if e.Index != 7 || e.Time != "2026-08-18 03:03:08" || e.TriggerSource != 0x1001 || e.TriggerSourceName == "" || e.ParamListVersion != "00000001" {
- t.Errorf("entry 7: %+v", e)
- }
- if _, err := ParseRealtime(3, sampleDirectoryHex[:40]); err == nil {
- t.Error("truncated directory should fail")
- }
- }
- func TestParseBasicInfo(t *testing.T) {
- b, err := ParseRealtime(2, "01010000000111941194060004000200")
- if err != nil {
- t.Fatal(err)
- }
- bi := b.(BasicInfo)
- if bi.SampleFrequency != 4500 || bi.TotalPoints != 1536 || bi.PrePoints != 1024 || bi.PostPoints != 512 {
- t.Errorf("basic info: %+v", bi)
- }
- }
- func loadSampleArchive(t *testing.T) []byte {
- t.Helper()
- data, err := os.ReadFile(sampleArchive)
- if err != nil {
- t.Skipf("sample archive not found: %v", err)
- }
- return data
- }
- func TestParseSampleArchive(t *testing.T) {
- records, err := ParseArchive(loadSampleArchive(t), proto.DeviceTypeAGate)
- if err != nil {
- t.Fatal(err)
- }
- if len(records) != 1 {
- t.Fatalf("records = %d", len(records))
- }
- rec := records[0]
- if rec.Batch != "2026-10-14_05_46_20" || rec.Sn != "AFCI_TEST_0713" || rec.RecordIndex != 7 {
- t.Errorf("record identity: %s %s %d", rec.Batch, rec.Sn, rec.RecordIndex)
- }
- f := rec.Failure
- if f.DeclaredLength != 512 || f.ParsedBytes != 308 {
- t.Errorf("length: declared=%d parsed=%d", f.DeclaredLength, f.ParsedBytes)
- }
- if f.Index != 7 || f.Time != "2026-08-18 03:03:08" || f.TriggerSource != 0x1001 {
- t.Errorf("header: %+v", f)
- }
- if f.Basic.SampleFrequency != 4500 || f.Basic.TotalPoints != 1536 || f.Basic.PrePoints != 1024 {
- t.Errorf("basic: %+v", f.Basic)
- }
- if f.DcdcVersion != "EMS_SL:V10R80B21D00" || f.InvVersion != "" || f.FpgaVersion != "" {
- t.Errorf("versions: %q %q %q", f.DcdcVersion, f.InvVersion, f.FpgaVersion)
- }
- if len(f.Snapshots) != 6 || *f.Snapshots[0].Value != 2049 || len(f.SampleMeanings) != 16 || len(f.ConfigSamples) != 4 {
- t.Errorf("channels: snap=%d samp=%d cfg=%d", len(f.Snapshots), len(f.SampleMeanings), len(f.ConfigSamples))
- }
- w := rec.Waveform
- if len(w.Channels) != 16 || w.Points != 1536 {
- t.Fatalf("waveform shape: %d × %d", len(w.Channels), w.Points)
- }
- // 故障字通道在第 1024 点跳变
- ch0 := w.Channels[0].Values
- if ch0[1023] != 0 || ch0[1024] != 0x0801 {
- t.Errorf("ch0 around fault: %d %d", ch0[1023], ch0[1024])
- }
- // 正弦通道按 int16 解释,含负值
- if w.Channels[4].Values[0] != -396 {
- t.Errorf("ch4[0] = %d", w.Channels[4].Values[0])
- }
- if w.Channels[12].Configurable == nil || w.Channels[12].Configurable.Address != 0x14990 || w.Channels[11].Configurable != nil {
- t.Error("configurable channels should be the last 4")
- }
- if w.TimeOffsetMs(1024) != 0 {
- t.Errorf("fault time offset: %v", w.TimeOffsetMs(1024))
- }
- }
- func TestPEKeepsVersions(t *testing.T) {
- files, err := ExtractArchive(loadSampleArchive(t))
- if err != nil {
- t.Fatal(err)
- }
- rec, err := ParseRecordFiles(files[0], proto.DeviceTypePE)
- if err != nil {
- t.Fatal(err)
- }
- if rec.Failure.InvVersion == "" {
- t.Error("pe should parse INV version field")
- }
- }
- func TestBuildCSVZip(t *testing.T) {
- records, err := ParseArchive(loadSampleArchive(t), proto.DeviceTypeAGate)
- if err != nil {
- t.Fatal(err)
- }
- data, err := BuildCSVZip(records)
- if err != nil {
- t.Fatal(err)
- }
- zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
- if err != nil {
- t.Fatal(err)
- }
- if len(zr.File) != 2 {
- t.Fatalf("zip files = %d", len(zr.File))
- }
- var wf bytes.Buffer
- if err := WriteWaveformCSV(&wf, records[0]); err != nil {
- t.Fatal(err)
- }
- lines := strings.Split(strings.TrimSpace(wf.String()), "\n")
- if len(lines) != 1537 {
- t.Errorf("waveform csv lines = %d", len(lines))
- }
- if !strings.HasPrefix(lines[1025], "1024,0.000,2049,") {
- t.Errorf("fault row: %s", lines[1025])
- }
- }
- func TestRejectBadArchive(t *testing.T) {
- if _, err := ParseArchive([]byte("not an archive"), proto.DeviceTypeAGate); err == nil {
- t.Error("garbage should fail")
- }
- }
- func TestConfigurableChannelModes(t *testing.T) {
- // code=1:最大/当前记录数、两个时间、快照/采样/可配快照为空,可配采样 2 项(默认方式 Uint16、代码方式 0x1234)
- hexData := "1005" + "07EA0A100C0000" + "07EA0A100C0000" + "0000" + "0000" + "0000" +
- "0202" + "0001" + "00014990" + "0010" + "12340000"
- v, err := ParseRealtime(1, hexData)
- if err != nil {
- t.Fatal(err)
- }
- cs := v.(Overview).ConfigSamples
- if len(cs) != 2 || cs[0].Mode != 0 || cs[0].TypeName != "Uint16" || cs[0].Address != 0x14990 ||
- cs[1].Mode != 1 || ConfigurableDesc(cs[1]) != "代码0x1234" {
- t.Errorf("configurable: %+v", cs)
- }
- }
|