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  }