package greenled import ( "context" "os" "path/filepath" "testing" "time" ) func TestSysfsControllerMapsSerialChannelsInOrder(t *testing.T) { root := t.TempDir() for channel := 1; channel <= ChannelCount; channel++ { state, err := stateForChannel(channel) if err != nil { t.Fatalf("map channel %d: %v", channel, err) } if err := os.MkdirAll(filepath.Join(root, state.LEDName), 0o755); err != nil { t.Fatalf("create %s: %v", state.LEDName, err) } if err := os.WriteFile(filepath.Join(root, state.LEDName, "brightness"), []byte("0\n"), 0o644); err != nil { t.Fatalf("seed %s: %v", state.LEDName, err) } } controller := NewSysfsController(root) states, err := controller.States(context.Background()) if err != nil { t.Fatalf("list states: %v", err) } if len(states) != ChannelCount { t.Fatalf("state count = %d, want %d", len(states), ChannelCount) } for index, state := range states { channel := index + 1 if state.Channel != "CH"+twoDigits(channel) || state.I2CBus != 3 || state.I2CAddress != expectedAddress(channel) || state.GPIOOffset != (channel-1)%8 || !state.Available || state.Forced { t.Fatalf("state for serial CH%02d = %#v", channel, state) } } } func TestSysfsControllerForcesAndPulsesThenReleases(t *testing.T) { root := t.TempDir() state, err := stateForChannel(9) if err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Join(root, state.LEDName), 0o755); err != nil { t.Fatal(err) } brightnessPath := filepath.Join(root, state.LEDName, "brightness") if err := os.WriteFile(brightnessPath, []byte("0\n"), 0o644); err != nil { t.Fatal(err) } controller := NewSysfsController(root) forced, err := controller.SetForce(context.Background(), "ch09", true) if err != nil || !forced.Forced || forced.Channel != "CH09" { t.Fatalf("force CH09 = %#v, %v", forced, err) } if value, err := os.ReadFile(brightnessPath); err != nil || string(value) != "1\n" { t.Fatalf("brightness after force = %q, %v", value, err) } released, err := controller.Pulse(context.Background(), "CH09", MinPulseDuration) if err != nil || released.Forced { t.Fatalf("pulse CH09 = %#v, %v", released, err) } if value, err := os.ReadFile(brightnessPath); err != nil || string(value) != "0\n" { t.Fatalf("brightness after pulse = %q, %v", value, err) } } func TestSysfsControllerRejectsInvalidChannelAndPulseDuration(t *testing.T) { controller := NewSysfsController(t.TempDir()) if _, err := controller.SetForce(context.Background(), "CH49", true); err == nil { t.Fatal("CH49 force unexpectedly succeeded") } if _, err := controller.Pulse(context.Background(), "CH01", 99*time.Millisecond); err == nil { t.Fatal("short pulse unexpectedly succeeded") } } func twoDigits(value int) string { if value < 10 { return "0" + string(rune('0'+value)) } return string(rune('0'+value/10)) + string(rune('0'+value%10)) } func expectedAddress(channel int) string { return "0x" + []string{"20", "21", "22", "23", "24", "25"}[(channel-1)/8] }