gobot.io/x/gobot/v2@v2.1.0/system/digitalpin_gpiod_test.go (about)

     1  package system
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"gobot.io/x/gobot/v2"
     9  	"gobot.io/x/gobot/v2/gobottest"
    10  )
    11  
    12  var _ gobot.DigitalPinner = (*digitalPinGpiod)(nil)
    13  var _ gobot.DigitalPinValuer = (*digitalPinGpiod)(nil)
    14  var _ gobot.DigitalPinOptioner = (*digitalPinGpiod)(nil)
    15  var _ gobot.DigitalPinOptionApplier = (*digitalPinGpiod)(nil)
    16  
    17  func Test_newDigitalPinGpiod(t *testing.T) {
    18  	// arrange
    19  	const (
    20  		chip  = "gpiochip0"
    21  		pin   = 17
    22  		label = "gobotio17"
    23  	)
    24  	// act
    25  	d := newDigitalPinGpiod(chip, pin)
    26  	// assert
    27  	gobottest.Refute(t, d, nil)
    28  	gobottest.Assert(t, d.chipName, chip)
    29  	gobottest.Assert(t, d.pin, pin)
    30  	gobottest.Assert(t, d.label, label)
    31  	gobottest.Assert(t, d.direction, IN)
    32  	gobottest.Assert(t, d.outInitialState, 0)
    33  }
    34  
    35  func Test_newDigitalPinGpiodWithOptions(t *testing.T) {
    36  	// This is a general test, that options are applied by using "newDigitalPinGpiod" with the WithPinLabel() option.
    37  	// All other configuration options will be tested in tests for "digitalPinConfig".
    38  	//
    39  	// arrange
    40  	const label = "my own label"
    41  	// act
    42  	dp := newDigitalPinGpiod("", 9, WithPinLabel(label))
    43  	// assert
    44  	gobottest.Assert(t, dp.label, label)
    45  }
    46  
    47  func TestApplyOptions(t *testing.T) {
    48  	var tests = map[string]struct {
    49  		changed          []bool
    50  		simErr           error
    51  		wantReconfigured int
    52  		wantErr          error
    53  	}{
    54  		"both_changed": {
    55  			changed:          []bool{true, true},
    56  			wantReconfigured: 1,
    57  		},
    58  		"first_changed": {
    59  			changed:          []bool{true, false},
    60  			wantReconfigured: 1,
    61  		},
    62  		"second_changed": {
    63  			changed:          []bool{false, true},
    64  			wantReconfigured: 1,
    65  		},
    66  		"none_changed": {
    67  			changed:          []bool{false, false},
    68  			simErr:           fmt.Errorf("error not raised"),
    69  			wantReconfigured: 0,
    70  		},
    71  		"error_on_change": {
    72  			changed:          []bool{false, true},
    73  			simErr:           fmt.Errorf("error raised"),
    74  			wantReconfigured: 1,
    75  			wantErr:          fmt.Errorf("error raised"),
    76  		},
    77  	}
    78  	for name, tc := range tests {
    79  		t.Run(name, func(t *testing.T) {
    80  			// currently the gpiod.Chip has no interface for RequestLine(),
    81  			// so we can only test without trigger of real reconfigure
    82  			// arrange
    83  			orgReconf := digitalPinGpiodReconfigure
    84  			defer func() { digitalPinGpiodReconfigure = orgReconf }()
    85  
    86  			inputForced := true
    87  			reconfigured := 0
    88  			digitalPinGpiodReconfigure = func(d *digitalPinGpiod, forceInput bool) error {
    89  				inputForced = forceInput
    90  				reconfigured++
    91  				return tc.simErr
    92  			}
    93  			d := &digitalPinGpiod{digitalPinConfig: &digitalPinConfig{direction: "in"}}
    94  			optionFunction1 := func(gobot.DigitalPinOptioner) bool {
    95  				d.digitalPinConfig.direction = "test"
    96  				return tc.changed[0]
    97  			}
    98  			optionFunction2 := func(gobot.DigitalPinOptioner) bool {
    99  				d.digitalPinConfig.drive = 15
   100  				return tc.changed[1]
   101  			}
   102  			// act
   103  			err := d.ApplyOptions(optionFunction1, optionFunction2)
   104  			// assert
   105  			gobottest.Assert(t, err, tc.wantErr)
   106  			gobottest.Assert(t, d.digitalPinConfig.direction, "test")
   107  			gobottest.Assert(t, d.digitalPinConfig.drive, 15)
   108  			gobottest.Assert(t, reconfigured, tc.wantReconfigured)
   109  			if reconfigured > 0 {
   110  				gobottest.Assert(t, inputForced, false)
   111  			}
   112  		})
   113  	}
   114  }
   115  
   116  func TestExport(t *testing.T) {
   117  	var tests = map[string]struct {
   118  		simErr           error
   119  		wantReconfigured int
   120  		wantErr          error
   121  	}{
   122  		"no_err": {
   123  			wantReconfigured: 1,
   124  		},
   125  		"error": {
   126  			wantReconfigured: 1,
   127  			simErr:           fmt.Errorf("reconfigure error"),
   128  			wantErr:          fmt.Errorf("gpiod.Export(): reconfigure error"),
   129  		},
   130  	}
   131  	for name, tc := range tests {
   132  		t.Run(name, func(t *testing.T) {
   133  			// currently the gpiod.Chip has no interface for RequestLine(),
   134  			// so we can only test without trigger of real reconfigure
   135  			// arrange
   136  			orgReconf := digitalPinGpiodReconfigure
   137  			defer func() { digitalPinGpiodReconfigure = orgReconf }()
   138  
   139  			inputForced := true
   140  			reconfigured := 0
   141  			digitalPinGpiodReconfigure = func(d *digitalPinGpiod, forceInput bool) error {
   142  				inputForced = forceInput
   143  				reconfigured++
   144  				return tc.simErr
   145  			}
   146  			d := &digitalPinGpiod{}
   147  			// act
   148  			err := d.Export()
   149  			// assert
   150  			gobottest.Assert(t, err, tc.wantErr)
   151  			gobottest.Assert(t, inputForced, false)
   152  			gobottest.Assert(t, reconfigured, tc.wantReconfigured)
   153  		})
   154  	}
   155  }
   156  
   157  func TestUnexport(t *testing.T) {
   158  	var tests = map[string]struct {
   159  		simNoLine        bool
   160  		simReconfErr     error
   161  		simCloseErr      error
   162  		wantReconfigured int
   163  		wantErr          error
   164  	}{
   165  		"no_line_no_err": {
   166  			simNoLine:        true,
   167  			wantReconfigured: 0,
   168  		},
   169  		"no_line_with_err": {
   170  			simNoLine:        true,
   171  			simReconfErr:     fmt.Errorf("reconfigure error"),
   172  			wantReconfigured: 0,
   173  		},
   174  		"no_err": {
   175  			wantReconfigured: 1,
   176  		},
   177  		"error_reconfigure": {
   178  			wantReconfigured: 1,
   179  			simReconfErr:     fmt.Errorf("reconfigure error"),
   180  			wantErr:          fmt.Errorf("reconfigure error"),
   181  		},
   182  		"error_close": {
   183  			wantReconfigured: 1,
   184  			simCloseErr:      fmt.Errorf("close error"),
   185  			wantErr:          fmt.Errorf("gpiod.Unexport()-line.Close(): close error"),
   186  		},
   187  	}
   188  	for name, tc := range tests {
   189  		t.Run(name, func(t *testing.T) {
   190  			// currently the gpiod.Chip has no interface for RequestLine(),
   191  			// so we can only test without trigger of real reconfigure
   192  			// arrange
   193  			orgReconf := digitalPinGpiodReconfigure
   194  			defer func() { digitalPinGpiodReconfigure = orgReconf }()
   195  
   196  			inputForced := false
   197  			reconfigured := 0
   198  			digitalPinGpiodReconfigure = func(d *digitalPinGpiod, forceInput bool) error {
   199  				inputForced = forceInput
   200  				reconfigured++
   201  				return tc.simReconfErr
   202  			}
   203  			dp := newDigitalPinGpiod("", 4)
   204  			if !tc.simNoLine {
   205  				dp.line = &lineMock{simCloseErr: tc.simCloseErr}
   206  			}
   207  			// act
   208  			err := dp.Unexport()
   209  			// assert
   210  			gobottest.Assert(t, err, tc.wantErr)
   211  			gobottest.Assert(t, reconfigured, tc.wantReconfigured)
   212  			if reconfigured > 0 {
   213  				gobottest.Assert(t, inputForced, true)
   214  			}
   215  		})
   216  	}
   217  }
   218  
   219  func TestWrite(t *testing.T) {
   220  	var tests = map[string]struct {
   221  		val     int
   222  		simErr  error
   223  		want    int
   224  		wantErr []string
   225  	}{
   226  		"write_zero": {
   227  			val:  0,
   228  			want: 0,
   229  		},
   230  		"write_one": {
   231  			val:  1,
   232  			want: 1,
   233  		},
   234  		"write_minus_one": {
   235  			val:  -1,
   236  			want: 0,
   237  		},
   238  		"write_two": {
   239  			val:  2,
   240  			want: 1,
   241  		},
   242  		"write_with_err": {
   243  			simErr:  fmt.Errorf("a write err"),
   244  			wantErr: []string{"a write err", "gpiod.Write"},
   245  		},
   246  	}
   247  	for name, tc := range tests {
   248  		t.Run(name, func(t *testing.T) {
   249  			// arrange
   250  			dp := newDigitalPinGpiod("", 4)
   251  			lm := &lineMock{lastVal: 10, simSetErr: tc.simErr}
   252  			dp.line = lm
   253  			// act
   254  			err := dp.Write(tc.val)
   255  			// assert
   256  			if tc.wantErr != nil {
   257  				for _, want := range tc.wantErr {
   258  					gobottest.Assert(t, strings.Contains(err.Error(), want), true)
   259  				}
   260  			} else {
   261  				gobottest.Assert(t, err, nil)
   262  			}
   263  			gobottest.Assert(t, lm.lastVal, tc.want)
   264  		})
   265  	}
   266  }
   267  
   268  func TestRead(t *testing.T) {
   269  	var tests = map[string]struct {
   270  		simVal  int
   271  		simErr  error
   272  		wantErr []string
   273  	}{
   274  		"read_ok": {
   275  			simVal: 3,
   276  		},
   277  		"write_with_err": {
   278  			simErr:  fmt.Errorf("a read err"),
   279  			wantErr: []string{"a read err", "gpiod.Read"},
   280  		},
   281  	}
   282  	for name, tc := range tests {
   283  		t.Run(name, func(t *testing.T) {
   284  			// arrange
   285  			dp := newDigitalPinGpiod("", 4)
   286  			lm := &lineMock{lastVal: tc.simVal, simValueErr: tc.simErr}
   287  			dp.line = lm
   288  			// act
   289  			got, err := dp.Read()
   290  			// assert
   291  			if tc.wantErr != nil {
   292  				for _, want := range tc.wantErr {
   293  					gobottest.Assert(t, strings.Contains(err.Error(), want), true)
   294  				}
   295  			} else {
   296  				gobottest.Assert(t, err, nil)
   297  			}
   298  			gobottest.Assert(t, tc.simVal, got)
   299  		})
   300  	}
   301  }
   302  
   303  type lineMock struct {
   304  	lastVal     int
   305  	simSetErr   error
   306  	simValueErr error
   307  	simCloseErr error
   308  }
   309  
   310  func (lm *lineMock) SetValue(value int) error { lm.lastVal = value; return lm.simSetErr }
   311  func (lm *lineMock) Value() (int, error)      { return lm.lastVal, lm.simValueErr }
   312  func (lm *lineMock) Close() error             { return lm.simCloseErr }