blob: 34686179cbe957c63bd0646b17990053d2fa52da [file] [edit]
// Copyright 2026 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"strings"
"testing"
)
func TestParseCSV_Valid(t *testing.T) {
csvData := `PinNumber,NetName,Pull,Dir,IO,Enum,PinTypeOverride,InitialCondition,NetNameOverride,Voltage
A1,EC_WP_ODL,UP,IN,--,GPIO_WRITE_PROTECT_L,INPUT_L,,EC_WP_L,3.3V
A2,GPIO_TEST,N,OUT,TTL,,OUTPUT_H,HIGH,,1.8V
A3,EC_I2C_SENSOR_SCL,N,IO,OD,I2C_SENSOR_SCL,I2C_CLOCK,,,"3.3 V"
`
configs, err := ParseCSV(strings.NewReader(csvData), "")
if err != nil {
t.Fatalf("ParseCSV failed: %v", err)
}
if len(configs) != 3 {
t.Fatalf("Expected 3 configs, got %d", len(configs))
}
c1 := configs[0]
if c1.PinNumber != "A1" || c1.NetName != "EC_WP_ODL" || c1.Pull != "UP" || c1.Dir != "IN" || c1.Enum != "GPIO_WRITE_PROTECT_L" || c1.PinTypeOverride != "INPUT_L" || c1.NetNameOverride != "EC_WP_L" || c1.Voltage != "3.3V" {
t.Errorf("Unexpected config 1: %+v", c1)
}
c2 := configs[1]
if c2.PinNumber != "A2" || c2.NetName != "GPIO_TEST" || c2.Pull != "N" || c2.Dir != "OUT" || c2.InitialCondition != "HIGH" {
t.Errorf("Unexpected config 2: %+v", c2)
}
c3 := configs[2]
expectedEnum := "I2C_SENSOR_SCL"
if c3.Enum != expectedEnum {
t.Errorf("Expected Enum %q, got %q", expectedEnum, c3.Enum)
}
}
func TestParseCSV_MultiVariantColumn(t *testing.T) {
csvData := `Signal Name,Subsystem,Description,DIR,Int,I/O Type,Internal PU,I/O Voltage,NPCX99 (NPCX),Type,Enum
GSC_EC_PWR_BTN_ODL,GSC,Power Button input,IN,both,--,Y,3.30 V,E7,INPUT_PU,GPIO_POWER_BUTTON_L
NC_UNCONNECTED,MISC,Unused pin,IN,no,--,N,1.80 V,F1,OTHER,
EC_EDP_BL_EN_OD,DISP,Backlight enable,OUT,no,OD,N,3.30 V,E10,OUTPUT_ODR,GPIO_ENABLE_BACKLIGHT
`
configs, err := ParseCSV(strings.NewReader(csvData), "NPCX")
if err != nil {
t.Fatalf("ParseCSV failed: %v", err)
}
if len(configs) != 2 {
t.Fatalf("Expected 2 configs (NC skipped), got %d", len(configs))
}
if configs[0].PinNumber != "E7" || configs[0].NetName != "GSC_EC_PWR_BTN_ODL" || configs[0].Pull != "UP" || configs[0].PinTypeOverride != "INPUT_L" {
t.Errorf("Unexpected config 0: %+v", configs[0])
}
if configs[1].PinNumber != "E10" || configs[1].NetName != "EC_EDP_BL_EN_OD" || configs[1].InitialCondition != "HIGH" {
t.Errorf("Unexpected config 1: %+v", configs[1])
}
}
func TestParseCSV_MissingRequiredColumn(t *testing.T) {
csvData := `PinName,Pull,Dir,Enum
A1,UP,IN,GPIO_TEST
`
_, err := ParseCSV(strings.NewReader(csvData), "")
if err == nil {
t.Error("Expected error for missing NetName column, got nil")
}
}
func TestParseCSV_CorruptAndEmptyInputs(t *testing.T) {
// Empty CSV Reader
_, err := ParseCSV(strings.NewReader(""), "")
if err == nil || !strings.Contains(err.Error(), "failed to read header") {
t.Errorf("Expected header read error for empty CSV, got %v", err)
}
// Broken CSV format (unmatched quote)
corruptCSV := "PinNumber,NetName\nA1,\"UNCLOSED_QUOTE\n"
_, err = ParseCSV(strings.NewReader(corruptCSV), "")
if err == nil || !strings.Contains(err.Error(), "failed to read row") {
t.Errorf("Expected row read error for corrupt CSV, got %v", err)
}
// Missing primary pin header with explicit pinCol
missingPinCSV := "HeaderNumber,NetName\nA1,EC_FOO\n"
_, err = ParseCSV(strings.NewReader(missingPinCSV), "SpecifiedCol")
if err == nil || !strings.Contains(err.Error(), "missing required column for Pin Number") {
t.Errorf("Expected missing column error, got %v", err)
}
}
func TestParseCSV_SignalFilteringExclusions(t *testing.T) {
csvData := `PinNumber,NetName
A1,NC
A2,NC (TP)
A3,NC(TP)
A4,NC_TP
A5,NC_SPARE
A6,NC 123
A7,OPEN
A8,(OPEN)
A9,FLOATING
B1,"(OPEN, FLOATING)"
B2,N/A
B3,NA
B4,
B5,VALID_SIGNAL
`
configs, err := ParseCSV(strings.NewReader(csvData), "")
if err != nil {
t.Fatalf("ParseCSV failed: %v", err)
}
if len(configs) != 1 {
t.Fatalf("Expected only 1 valid config (VALID_SIGNAL), got %d (configs: %+v)", len(configs), configs)
}
if configs[0].NetName != "VALID_SIGNAL" || configs[0].PinNumber != "B5" {
t.Errorf("Unexpected remaining config: %+v", configs[0])
}
}
func TestParseCSV_FallbackPinNumberResolution(t *testing.T) {
csvData := `NPCX993 (Main),NPCX993 (Board),NetName,IO,Enum
N/A,B3,EC_I2C_SCL,I2C,I2C_PORT_0
N/A,B4,EC_GPIO_IN,IN,
--,,EC_PWM_FAN,PWM,
`
configs, err := ParseCSV(strings.NewReader(csvData), "Main")
if err != nil {
t.Fatalf("ParseCSV failed: %v", err)
}
// Primary N/A for peripheral I2C should fall back to Secondary B3
if len(configs) != 1 {
t.Fatalf("Expected 1 valid config via fallback, got %d", len(configs))
}
if configs[0].PinNumber != "B3" || configs[0].NetName != "EC_I2C_SCL" {
t.Errorf("Fallback failed, got config: %+v", configs[0])
}
}
func TestParseCSV_TypeAndConditionNormalizations(t *testing.T) {
csvData := `PinNumber,NetName,Dir,PinTypeOverride,InitialCondition
A1,EC_WP_ODL,IN,INPUT_PU,
A2,EC_SENSOR_LOW,IN,INPUT,
A3,EC_RST_L,OUT,OUTPUT_L,
A4,EC_POWER_ON,OUT,OUTPUT_H,
A5,EC_PCH_WAKE_ODL,OUT,,
`
configs, err := ParseCSV(strings.NewReader(csvData), "")
if err != nil {
t.Fatalf("ParseCSV failed: %v", err)
}
if len(configs) != 5 {
t.Fatalf("Expected 5 configs, got %d", len(configs))
}
// A1: INPUT_PU + _ODL -> INPUT_L, Pull UP
if configs[0].PinTypeOverride != "INPUT_L" || configs[0].Pull != "UP" {
t.Errorf("Config A1 mismatch: %+v", configs[0])
}
// A2: INPUT + _LOW -> INPUT_L
if configs[1].PinTypeOverride != "INPUT_L" {
t.Errorf("Config A2 mismatch: %+v", configs[1])
}
// A3: OUTPUT_L -> InitialCondition LOW
if configs[2].InitialCondition != "LOW" {
t.Errorf("Config A3 initial condition expected LOW, got %q", configs[2].InitialCondition)
}
// A4: OUTPUT_H -> InitialCondition HIGH
if configs[3].InitialCondition != "HIGH" {
t.Errorf("Config A4 initial condition expected HIGH, got %q", configs[3].InitialCondition)
}
// A5: EC_PCH_WAKE_ODL special case -> InitialCondition HIGH
if configs[4].InitialCondition != "HIGH" {
t.Errorf("Config A5 initial condition expected HIGH, got %q", configs[4].InitialCondition)
}
}
func TestParseCSV_HeaderNormalization(t *testing.T) {
csvData := `"NPCX9
RVP",NetName
A1,EC_GPIO_A1
`
configs, err := ParseCSV(strings.NewReader(csvData), "NPCX9 RVP")
if err != nil {
t.Fatalf("ParseCSV failed: %v", err)
}
if len(configs) != 1 {
t.Fatalf("Expected 1 config, got %d", len(configs))
}
if configs[0].PinNumber != "A1" || configs[0].NetName != "EC_GPIO_A1" {
t.Errorf("Unexpected config: %+v", configs[0])
}
}