gobot.io/x/gobot/v2@v2.1.0/platforms/adaptors/digitalpinsadaptor_test.go (about) 1 package adaptors 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "testing" 8 "time" 9 10 "runtime" 11 "strconv" 12 "sync" 13 14 "gobot.io/x/gobot/v2" 15 "gobot.io/x/gobot/v2/drivers/gpio" 16 "gobot.io/x/gobot/v2/gobottest" 17 "gobot.io/x/gobot/v2/system" 18 ) 19 20 // make sure that this adaptor fulfills all the required interfaces 21 var _ gobot.DigitalPinnerProvider = (*DigitalPinsAdaptor)(nil) 22 var _ gpio.DigitalReader = (*DigitalPinsAdaptor)(nil) 23 var _ gpio.DigitalWriter = (*DigitalPinsAdaptor)(nil) 24 25 func initTestDigitalPinsAdaptorWithMockedFilesystem(mockPaths []string) (*DigitalPinsAdaptor, *system.MockFilesystem) { 26 sys := system.NewAccesser() 27 fs := sys.UseMockFilesystem(mockPaths) 28 a := NewDigitalPinsAdaptor(sys, testDigitalPinTranslator) 29 if err := a.Connect(); err != nil { 30 panic(err) 31 } 32 return a, fs 33 } 34 35 func testDigitalPinTranslator(pin string) (string, int, error) { 36 line, err := strconv.Atoi(pin) 37 if err != nil { 38 return "", 0, fmt.Errorf("not a valid pin") 39 } 40 line = line + 11 // just for tests 41 return "", line, err 42 } 43 44 func TestDigitalPinsWithGpiosActiveLow(t *testing.T) { 45 // This is a general test, that options are applied in constructor. Further tests for options 46 // can also be done by call of "WithOption(val)(d)". 47 // arrange 48 translate := func(pin string) (chip string, line int, err error) { return } 49 sys := system.NewAccesser() 50 // act 51 a := NewDigitalPinsAdaptor(sys, translate, WithGpiosActiveLow("1", "12", "33")) 52 // assert 53 gobottest.Assert(t, len(a.pinOptions), 3) 54 } 55 56 func TestDigitalPinsConnect(t *testing.T) { 57 translate := func(pin string) (chip string, line int, err error) { return } 58 sys := system.NewAccesser() 59 60 a := NewDigitalPinsAdaptor(sys, translate) 61 gobottest.Assert(t, a.pins, (map[string]gobot.DigitalPinner)(nil)) 62 63 _, err := a.DigitalRead("13") 64 gobottest.Assert(t, err.Error(), "not connected for pin 13") 65 66 err = a.DigitalWrite("7", 1) 67 gobottest.Assert(t, err.Error(), "not connected for pin 7") 68 69 err = a.Connect() 70 gobottest.Assert(t, err, nil) 71 gobottest.Refute(t, a.pins, (map[string]gobot.DigitalPinner)(nil)) 72 gobottest.Assert(t, len(a.pins), 0) 73 } 74 75 func TestDigitalPinsFinalize(t *testing.T) { 76 // arrange 77 mockedPaths := []string{ 78 "/sys/class/gpio/export", 79 "/sys/class/gpio/unexport", 80 "/sys/class/gpio/gpio14/direction", 81 "/sys/class/gpio/gpio14/value", 82 } 83 sys := system.NewAccesser() 84 fs := sys.UseMockFilesystem(mockedPaths) 85 a := NewDigitalPinsAdaptor(sys, testDigitalPinTranslator) 86 // assert that finalize before connect is working 87 gobottest.Assert(t, a.Finalize(), nil) 88 // arrange 89 gobottest.Assert(t, a.Connect(), nil) 90 gobottest.Assert(t, a.DigitalWrite("3", 1), nil) 91 gobottest.Assert(t, len(a.pins), 1) 92 // act 93 err := a.Finalize() 94 // assert 95 gobottest.Assert(t, err, nil) 96 gobottest.Assert(t, len(a.pins), 0) 97 // assert that finalize after finalize is working 98 gobottest.Assert(t, a.Finalize(), nil) 99 // arrange missing sysfs file 100 gobottest.Assert(t, a.Connect(), nil) 101 gobottest.Assert(t, a.DigitalWrite("3", 2), nil) 102 delete(fs.Files, "/sys/class/gpio/unexport") 103 err = a.Finalize() 104 gobottest.Assert(t, strings.Contains(err.Error(), "/sys/class/gpio/unexport: no such file"), true) 105 } 106 107 func TestDigitalPinsReConnect(t *testing.T) { 108 // arrange 109 mockedPaths := []string{ 110 "/sys/class/gpio/export", 111 "/sys/class/gpio/unexport", 112 "/sys/class/gpio/gpio15/direction", 113 "/sys/class/gpio/gpio15/value", 114 } 115 a, _ := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) 116 gobottest.Assert(t, a.DigitalWrite("4", 1), nil) 117 gobottest.Assert(t, len(a.pins), 1) 118 gobottest.Assert(t, a.Finalize(), nil) 119 // act 120 err := a.Connect() 121 // assert 122 gobottest.Assert(t, err, nil) 123 gobottest.Refute(t, a.pins, nil) 124 gobottest.Assert(t, len(a.pins), 0) 125 } 126 127 func TestDigitalIO(t *testing.T) { 128 mockedPaths := []string{ 129 "/sys/class/gpio/export", 130 "/sys/class/gpio/unexport", 131 "/sys/class/gpio/gpio25/value", 132 "/sys/class/gpio/gpio25/direction", 133 } 134 a, _ := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) 135 136 err := a.DigitalWrite("14", 1) 137 gobottest.Assert(t, err, nil) 138 139 i, err := a.DigitalRead("14") 140 gobottest.Assert(t, err, nil) 141 gobottest.Assert(t, i, 1) 142 } 143 144 func TestDigitalRead(t *testing.T) { 145 // arrange 146 mockedPaths := []string{ 147 "/sys/class/gpio/export", 148 "/sys/class/gpio/unexport", 149 "/sys/class/gpio/gpio24/value", 150 "/sys/class/gpio/gpio24/direction", 151 } 152 a, fs := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) 153 fs.Files["/sys/class/gpio/gpio24/value"].Contents = "1" 154 155 // assert read correct value without error 156 i, err := a.DigitalRead("13") 157 gobottest.Assert(t, err, nil) 158 gobottest.Assert(t, i, 1) 159 160 // assert error bubbling for read errors 161 fs.WithReadError = true 162 _, err = a.DigitalRead("13") 163 gobottest.Assert(t, err, errors.New("read error")) 164 165 // assert error bubbling for write errors 166 fs.WithWriteError = true 167 _, err = a.DigitalRead("7") 168 gobottest.Assert(t, err, errors.New("write error")) 169 } 170 171 func TestDigitalReadWithGpiosActiveLow(t *testing.T) { 172 mockedPaths := []string{ 173 "/sys/class/gpio/export", 174 "/sys/class/gpio/unexport", 175 "/sys/class/gpio/gpio25/value", 176 "/sys/class/gpio/gpio25/direction", 177 "/sys/class/gpio/gpio25/active_low", 178 "/sys/class/gpio/gpio26/value", 179 "/sys/class/gpio/gpio26/direction", 180 } 181 a, fs := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) 182 fs.Files["/sys/class/gpio/gpio25/value"].Contents = "1" 183 fs.Files["/sys/class/gpio/gpio25/active_low"].Contents = "5" 184 fs.Files["/sys/class/gpio/gpio26/value"].Contents = "0" 185 WithGpiosActiveLow("14")(a) 186 // creates a new pin without inverted logic 187 if _, err := a.DigitalRead("15"); err != nil { 188 panic(err) 189 } 190 fs.Add("/sys/class/gpio/gpio26/active_low") 191 fs.Files["/sys/class/gpio/gpio26/active_low"].Contents = "6" 192 WithGpiosActiveLow("15")(a) 193 // act 194 got1, err1 := a.DigitalRead("14") // for a new pin 195 got2, err2 := a.DigitalRead("15") // for an existing pin (calls ApplyOptions()) 196 // assert 197 gobottest.Assert(t, err1, nil) 198 gobottest.Assert(t, err2, nil) 199 gobottest.Assert(t, got1, 1) // there is no mechanism to negate mocked values 200 gobottest.Assert(t, got2, 0) 201 gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio25/active_low"].Contents, "1") 202 gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio26/active_low"].Contents, "1") 203 } 204 205 func TestDigitalWrite(t *testing.T) { 206 // arrange 207 mockedPaths := []string{ 208 "/sys/class/gpio/export", 209 "/sys/class/gpio/unexport", 210 "/sys/class/gpio/gpio18/value", 211 "/sys/class/gpio/gpio18/direction", 212 } 213 a, fs := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) 214 215 // assert write correct value without error and just ignore unsupported options 216 WithGpiosPullUp("7")(a) 217 WithGpiosOpenDrain("7")(a) 218 WithGpioEventOnFallingEdge("7", gpioEventHandler)(a) 219 err := a.DigitalWrite("7", 1) 220 gobottest.Assert(t, err, nil) 221 gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio18/value"].Contents, "1") 222 223 // assert second write to same pin without error and just ignore unsupported options 224 WithGpiosPullDown("7")(a) 225 WithGpiosOpenSource("7")(a) 226 WithGpioDebounce("7", 2*time.Second)(a) 227 WithGpioEventOnRisingEdge("7", gpioEventHandler)(a) 228 err = a.DigitalWrite("7", 1) 229 gobottest.Assert(t, err, nil) 230 231 // assert error on bad id 232 gobottest.Assert(t, a.DigitalWrite("notexist", 1), errors.New("not a valid pin")) 233 234 // assert error bubbling 235 fs.WithWriteError = true 236 err = a.DigitalWrite("7", 0) 237 gobottest.Assert(t, err, errors.New("write error")) 238 } 239 240 func TestDigitalWriteWithGpiosActiveLow(t *testing.T) { 241 // arrange 242 mockedPaths := []string{ 243 "/sys/class/gpio/export", 244 "/sys/class/gpio/unexport", 245 "/sys/class/gpio/gpio19/value", 246 "/sys/class/gpio/gpio19/direction", 247 "/sys/class/gpio/gpio19/active_low", 248 } 249 a, fs := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) 250 fs.Files["/sys/class/gpio/gpio19/active_low"].Contents = "5" 251 WithGpiosActiveLow("8")(a) 252 // act 253 err := a.DigitalWrite("8", 2) 254 // assert 255 gobottest.Assert(t, err, nil) 256 gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio19/value"].Contents, "2") 257 gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio19/active_low"].Contents, "1") 258 } 259 260 func TestDigitalPinConcurrency(t *testing.T) { 261 oldProcs := runtime.GOMAXPROCS(0) 262 runtime.GOMAXPROCS(8) 263 defer runtime.GOMAXPROCS(oldProcs) 264 265 translate := func(pin string) (string, int, error) { line, err := strconv.Atoi(pin); return "", line, err } 266 sys := system.NewAccesser() 267 268 for retry := 0; retry < 20; retry++ { 269 270 a := NewDigitalPinsAdaptor(sys, translate) 271 a.Connect() 272 var wg sync.WaitGroup 273 274 for i := 0; i < 20; i++ { 275 wg.Add(1) 276 pinAsString := strconv.Itoa(i) 277 go func(pin string) { 278 defer wg.Done() 279 a.DigitalPin(pin) 280 }(pinAsString) 281 } 282 283 wg.Wait() 284 } 285 } 286 287 func gpioEventHandler(o int, t time.Duration, et string, sn uint32, lsn uint32) { 288 // the handler should never execute, because used in outputs and not supported by sysfs 289 panic(fmt.Sprintf("event handler was called (%d, %d) unexpected for line %d with '%s' at %s!", sn, lsn, o, t, et)) 290 }