file_test.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package faultRecordFile
  2. import (
  3. "archive/zip"
  4. "bytes"
  5. "os"
  6. "strings"
  7. "testing"
  8. proto "fwh-toolbox-server/function/FaultRecordProtocol"
  9. )
  10. const (
  11. sampleArchive = "../../../docs/fault-record/2026-10-14_05_46_20.tar.bz2"
  12. // 2026-10-14 样本 379(cmd=0, code=3) 的 hexData
  13. sampleDirectoryHex = "0F0107E801010000220000000110020207EA070E0235230000000110010307EA080C0203050000000110010407EA080D1528280000000110010507EA080E0318100000000110010607EA0810033A050000000110010707EA08120303080000000110010807E90B18010B2E0000000110010907E90B1A152C060000000110010A07E705041601030000000110020B07E705041602210000000110020C07E705041603280000000110020D07E7050416042E0000000110020E07E70504161C0E0000000110020F07EA061D0F0723000000011001"
  14. )
  15. func TestParseDirectory(t *testing.T) {
  16. v, err := ParseRealtime(3, sampleDirectoryHex)
  17. if err != nil {
  18. t.Fatal(err)
  19. }
  20. list := v.([]DirectoryEntry)
  21. if len(list) != 15 {
  22. t.Fatalf("entries = %d", len(list))
  23. }
  24. e := list[6]
  25. if e.Index != 7 || e.Time != "2026-08-18 03:03:08" || e.TriggerSource != 0x1001 || e.TriggerSourceName == "" || e.ParamListVersion != "00000001" {
  26. t.Errorf("entry 7: %+v", e)
  27. }
  28. if _, err := ParseRealtime(3, sampleDirectoryHex[:40]); err == nil {
  29. t.Error("truncated directory should fail")
  30. }
  31. }
  32. func TestParseBasicInfo(t *testing.T) {
  33. b, err := ParseRealtime(2, "01010000000111941194060004000200")
  34. if err != nil {
  35. t.Fatal(err)
  36. }
  37. bi := b.(BasicInfo)
  38. if bi.SampleFrequency != 4500 || bi.TotalPoints != 1536 || bi.PrePoints != 1024 || bi.PostPoints != 512 {
  39. t.Errorf("basic info: %+v", bi)
  40. }
  41. }
  42. func loadSampleArchive(t *testing.T) []byte {
  43. t.Helper()
  44. data, err := os.ReadFile(sampleArchive)
  45. if err != nil {
  46. t.Skipf("sample archive not found: %v", err)
  47. }
  48. return data
  49. }
  50. func TestParseSampleArchive(t *testing.T) {
  51. records, err := ParseArchive(loadSampleArchive(t), proto.DeviceTypeAGate)
  52. if err != nil {
  53. t.Fatal(err)
  54. }
  55. if len(records) != 1 {
  56. t.Fatalf("records = %d", len(records))
  57. }
  58. rec := records[0]
  59. if rec.Batch != "2026-10-14_05_46_20" || rec.Sn != "AFCI_TEST_0713" || rec.RecordIndex != 7 {
  60. t.Errorf("record identity: %s %s %d", rec.Batch, rec.Sn, rec.RecordIndex)
  61. }
  62. f := rec.Failure
  63. if f.DeclaredLength != 512 || f.ParsedBytes != 308 {
  64. t.Errorf("length: declared=%d parsed=%d", f.DeclaredLength, f.ParsedBytes)
  65. }
  66. if f.Index != 7 || f.Time != "2026-08-18 03:03:08" || f.TriggerSource != 0x1001 {
  67. t.Errorf("header: %+v", f)
  68. }
  69. if f.Basic.SampleFrequency != 4500 || f.Basic.TotalPoints != 1536 || f.Basic.PrePoints != 1024 {
  70. t.Errorf("basic: %+v", f.Basic)
  71. }
  72. if f.DcdcVersion != "EMS_SL:V10R80B21D00" || f.InvVersion != "" || f.FpgaVersion != "" {
  73. t.Errorf("versions: %q %q %q", f.DcdcVersion, f.InvVersion, f.FpgaVersion)
  74. }
  75. if len(f.Snapshots) != 6 || *f.Snapshots[0].Value != 2049 || len(f.SampleMeanings) != 16 || len(f.ConfigSamples) != 4 {
  76. t.Errorf("channels: snap=%d samp=%d cfg=%d", len(f.Snapshots), len(f.SampleMeanings), len(f.ConfigSamples))
  77. }
  78. w := rec.Waveform
  79. if len(w.Channels) != 16 || w.Points != 1536 {
  80. t.Fatalf("waveform shape: %d × %d", len(w.Channels), w.Points)
  81. }
  82. // 故障字通道在第 1024 点跳变
  83. ch0 := w.Channels[0].Values
  84. if ch0[1023] != 0 || ch0[1024] != 0x0801 {
  85. t.Errorf("ch0 around fault: %d %d", ch0[1023], ch0[1024])
  86. }
  87. // 正弦通道按 int16 解释,含负值
  88. if w.Channels[4].Values[0] != -396 {
  89. t.Errorf("ch4[0] = %d", w.Channels[4].Values[0])
  90. }
  91. if w.Channels[12].Configurable == nil || w.Channels[12].Configurable.Address != 0x14990 || w.Channels[11].Configurable != nil {
  92. t.Error("configurable channels should be the last 4")
  93. }
  94. if w.TimeOffsetMs(1024) != 0 {
  95. t.Errorf("fault time offset: %v", w.TimeOffsetMs(1024))
  96. }
  97. }
  98. func TestPEKeepsVersions(t *testing.T) {
  99. files, err := ExtractArchive(loadSampleArchive(t))
  100. if err != nil {
  101. t.Fatal(err)
  102. }
  103. rec, err := ParseRecordFiles(files[0], proto.DeviceTypePE)
  104. if err != nil {
  105. t.Fatal(err)
  106. }
  107. if rec.Failure.InvVersion == "" {
  108. t.Error("pe should parse INV version field")
  109. }
  110. }
  111. func TestBuildCSVZip(t *testing.T) {
  112. records, err := ParseArchive(loadSampleArchive(t), proto.DeviceTypeAGate)
  113. if err != nil {
  114. t.Fatal(err)
  115. }
  116. data, err := BuildCSVZip(records)
  117. if err != nil {
  118. t.Fatal(err)
  119. }
  120. zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
  121. if err != nil {
  122. t.Fatal(err)
  123. }
  124. if len(zr.File) != 2 {
  125. t.Fatalf("zip files = %d", len(zr.File))
  126. }
  127. var wf bytes.Buffer
  128. if err := WriteWaveformCSV(&wf, records[0]); err != nil {
  129. t.Fatal(err)
  130. }
  131. lines := strings.Split(strings.TrimSpace(wf.String()), "\n")
  132. if len(lines) != 1537 {
  133. t.Errorf("waveform csv lines = %d", len(lines))
  134. }
  135. if !strings.HasPrefix(lines[1025], "1024,0.000,2049,") {
  136. t.Errorf("fault row: %s", lines[1025])
  137. }
  138. }
  139. func TestRejectBadArchive(t *testing.T) {
  140. if _, err := ParseArchive([]byte("not an archive"), proto.DeviceTypeAGate); err == nil {
  141. t.Error("garbage should fail")
  142. }
  143. }
  144. func TestConfigurableChannelModes(t *testing.T) {
  145. // code=1:最大/当前记录数、两个时间、快照/采样/可配快照为空,可配采样 2 项(默认方式 Uint16、代码方式 0x1234)
  146. hexData := "1005" + "07EA0A100C0000" + "07EA0A100C0000" + "0000" + "0000" + "0000" +
  147. "0202" + "0001" + "00014990" + "0010" + "12340000"
  148. v, err := ParseRealtime(1, hexData)
  149. if err != nil {
  150. t.Fatal(err)
  151. }
  152. cs := v.(Overview).ConfigSamples
  153. if len(cs) != 2 || cs[0].Mode != 0 || cs[0].TypeName != "Uint16" || cs[0].Address != 0x14990 ||
  154. cs[1].Mode != 1 || ConfigurableDesc(cs[1]) != "代码0x1234" {
  155. t.Errorf("configurable: %+v", cs)
  156. }
  157. }