gobot.io/x/gobot@v1.16.0/drivers/i2c/mcp23017_driver_test.go (about)

     1  package i2c
     2  
     3  import (
     4  	"errors"
     5  	"testing"
     6  
     7  	"gobot.io/x/gobot"
     8  	"gobot.io/x/gobot/gobottest"
     9  )
    10  
    11  var _ gobot.Driver = (*MCP23017Driver)(nil)
    12  
    13  var pinValPort = map[string]interface{}{
    14  	"pin":  uint8(7),
    15  	"val":  uint8(0),
    16  	"port": "A",
    17  }
    18  
    19  var pinPort = map[string]interface{}{
    20  	"pin":  uint8(7),
    21  	"port": "A",
    22  }
    23  
    24  func initTestMCP23017Driver(b uint8) (driver *MCP23017Driver) {
    25  	// create the driver without starting it
    26  	adaptor := newI2cTestAdaptor()
    27  	mcp := NewMCP23017Driver(adaptor, WithMCP23017Bank(b))
    28  	return mcp
    29  }
    30  
    31  func initTestMCP23017DriverWithStubbedAdaptor(b uint8) (*MCP23017Driver, *i2cTestAdaptor) {
    32  	// create the driver, ready to use for tests
    33  	adaptor := newI2cTestAdaptor()
    34  	mcp := NewMCP23017Driver(adaptor, WithMCP23017Bank(b))
    35  	mcp.Start()
    36  	return mcp, adaptor
    37  }
    38  
    39  func TestNewMCP23017Driver(t *testing.T) {
    40  	var bm interface{} = NewMCP23017Driver(newI2cTestAdaptor())
    41  	_, ok := bm.(*MCP23017Driver)
    42  	if !ok {
    43  		t.Errorf("NewMCP23017Driver() should have returned a *MCP23017Driver")
    44  	}
    45  
    46  	b := NewMCP23017Driver(newI2cTestAdaptor())
    47  	gobottest.Refute(t, b.Connection(), nil)
    48  }
    49  
    50  func TestNewMCP23017DriverBank(t *testing.T) {
    51  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Bank(1))
    52  	gobottest.Assert(t, b.mcpConf.bank, uint8(1))
    53  }
    54  
    55  func TestNewMCP23017DriverMirror(t *testing.T) {
    56  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Mirror(1))
    57  	gobottest.Assert(t, b.mcpConf.mirror, uint8(1))
    58  }
    59  
    60  func TestNewMCP23017DriverSeqop(t *testing.T) {
    61  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Seqop(1))
    62  	gobottest.Assert(t, b.mcpConf.seqop, uint8(1))
    63  }
    64  
    65  func TestNewMCP23017DriverDisslw(t *testing.T) {
    66  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Disslw(1))
    67  	gobottest.Assert(t, b.mcpConf.disslw, uint8(1))
    68  }
    69  
    70  func TestNewMCP23017DriverHaen(t *testing.T) {
    71  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Haen(1))
    72  	gobottest.Assert(t, b.mcpConf.haen, uint8(1))
    73  }
    74  
    75  func TestNewMCP23017DriverOdr(t *testing.T) {
    76  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Odr(1))
    77  	gobottest.Assert(t, b.mcpConf.odr, uint8(1))
    78  }
    79  
    80  func TestNewMCP23017DriverIntpol(t *testing.T) {
    81  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Intpol(1))
    82  	gobottest.Assert(t, b.mcpConf.intpol, uint8(1))
    83  }
    84  
    85  func TestNewMCP23017DriverForceRefresh(t *testing.T) {
    86  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017ForceRefresh(1))
    87  	gobottest.Assert(t, b.mcpBehav.forceRefresh, true)
    88  }
    89  
    90  func TestNewMCP23017DriverAutoIODirOff(t *testing.T) {
    91  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017AutoIODirOff(1))
    92  	gobottest.Assert(t, b.mcpBehav.autoIODirOff, true)
    93  }
    94  
    95  func TestMCP23017DriverStart(t *testing.T) {
    96  	// arrange
    97  	mcp := initTestMCP23017Driver(0)
    98  	// act & assert
    99  	gobottest.Assert(t, mcp.Start(), nil)
   100  }
   101  
   102  func TestMCP23017DriverStartErr(t *testing.T) {
   103  	// arrange
   104  	adaptor := newI2cTestAdaptor()
   105  	mcp := NewMCP23017Driver(adaptor, WithMCP23017Bank(0))
   106  	adaptor.i2cWriteImpl = func([]byte) (int, error) {
   107  		return 0, errors.New("write error")
   108  	}
   109  	// act
   110  	err := mcp.Start()
   111  	// assert
   112  	gobottest.Assert(t, err, errors.New("write error"))
   113  }
   114  
   115  func TestMCP23017DriverHalt(t *testing.T) {
   116  	mcp := initTestMCP23017Driver(0)
   117  	gobottest.Assert(t, mcp.Halt(), nil)
   118  }
   119  
   120  func TestMCP23017DriverCommandsWriteGPIO(t *testing.T) {
   121  	// arrange
   122  	mcp, _ := initTestMCP23017DriverWithStubbedAdaptor(0)
   123  	// act
   124  	result := mcp.Command("WriteGPIO")(pinValPort)
   125  	// assert
   126  	gobottest.Assert(t, result.(map[string]interface{})["err"], nil)
   127  }
   128  
   129  func TestMCP23017DriverCommandsReadGPIO(t *testing.T) {
   130  	// arrange
   131  	mcp, _ := initTestMCP23017DriverWithStubbedAdaptor(0)
   132  	// act
   133  	result := mcp.Command("ReadGPIO")(pinPort)
   134  	// assert
   135  	gobottest.Assert(t, result.(map[string]interface{})["err"], nil)
   136  }
   137  
   138  func TestMCP23017DriverWriteGPIO(t *testing.T) {
   139  	// sequence to write (we force the refresh by preset with inverse bit state):
   140  	// * read current state of IODIR (write reg, read val) => see also PinMode()
   141  	// * set IODIR of pin to input (manipulate val, write reg, write val) => see also PinMode()
   142  	// * read current state of OLAT (write reg, read val)
   143  	// * write OLAT (manipulate val, write reg, write val)
   144  	// arrange
   145  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   146  	for bitState := 0; bitState <= 1; bitState++ {
   147  		adaptor.written = []byte{} // reset writes of Start() and former test
   148  		// arrange some values
   149  		testPort := "A"
   150  		testPin := uint8(7)
   151  		wantReg1 := uint8(0x00)             // IODIRA
   152  		wantReg2 := uint8(0x14)             // OLATA
   153  		returnRead := []uint8{0xFF, 0xFF}   // emulate all IO's are inputs, emulate bit is on
   154  		wantReg1Val := returnRead[0] & 0x7F // IODIRA: bit 7 reset, all other untouched
   155  		wantReg2Val := returnRead[1] & 0x7F // OLATA: bit 7 reset, all other untouched
   156  		if bitState == 1 {
   157  			returnRead[1] = 0x7F               // emulate bit is off
   158  			wantReg2Val = returnRead[1] | 0x80 // OLATA: bit 7 set, all other untouched
   159  		}
   160  		// arrange reads
   161  		numCallsRead := 0
   162  		adaptor.i2cReadImpl = func(b []byte) (int, error) {
   163  			numCallsRead++
   164  			b[len(b)-1] = returnRead[numCallsRead-1]
   165  			return len(b), nil
   166  		}
   167  		// act
   168  		err := mcp.WriteGPIO(testPin, uint8(bitState), testPort)
   169  		// assert
   170  		gobottest.Assert(t, err, nil)
   171  		gobottest.Assert(t, len(adaptor.written), 6)
   172  		gobottest.Assert(t, adaptor.written[0], wantReg1)
   173  		gobottest.Assert(t, adaptor.written[1], wantReg1)
   174  		gobottest.Assert(t, adaptor.written[2], wantReg1Val)
   175  		gobottest.Assert(t, adaptor.written[3], wantReg2)
   176  		gobottest.Assert(t, adaptor.written[4], wantReg2)
   177  		gobottest.Assert(t, adaptor.written[5], wantReg2Val)
   178  		gobottest.Assert(t, numCallsRead, 2)
   179  	}
   180  }
   181  
   182  func TestMCP23017DriverWriteGPIONoRefresh(t *testing.T) {
   183  	// sequence to write with take advantage of refresh optimization (see forceRefresh):
   184  	// * read current state of IODIR (write reg, read val) => by PinMode()
   185  	// * read current state of OLAT (write reg, read val)
   186  	// arrange
   187  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   188  	for bitState := 0; bitState <= 1; bitState++ {
   189  		adaptor.written = []byte{} // reset writes of Start() and former test
   190  		// arrange some values
   191  		testPort := "B"
   192  		testPin := uint8(3)
   193  		wantReg1 := uint8(0x01)           // IODIRB
   194  		wantReg2 := uint8(0x15)           // OLATB
   195  		returnRead := []uint8{0xF7, 0xF7} // emulate all IO's are inputs except pin 3, emulate bit is already off
   196  		if bitState == 1 {
   197  			returnRead[1] = 0x08 // emulate bit is already on
   198  		}
   199  		// arrange reads
   200  		numCallsRead := 0
   201  		adaptor.i2cReadImpl = func(b []byte) (int, error) {
   202  			numCallsRead++
   203  			b[len(b)-1] = returnRead[numCallsRead-1]
   204  			return len(b), nil
   205  		}
   206  		// act
   207  		err := mcp.WriteGPIO(testPin, uint8(bitState), testPort)
   208  		// assert
   209  		gobottest.Assert(t, err, nil)
   210  		gobottest.Assert(t, len(adaptor.written), 2)
   211  		gobottest.Assert(t, adaptor.written[0], wantReg1)
   212  		gobottest.Assert(t, adaptor.written[1], wantReg2)
   213  		gobottest.Assert(t, numCallsRead, 2)
   214  	}
   215  }
   216  
   217  func TestMCP23017DriverWriteGPIONoAutoDir(t *testing.T) {
   218  	// sequence to write with suppressed automatic setting of IODIR:
   219  	// * read current state of OLAT (write reg, read val)
   220  	// * write OLAT (manipulate val, write reg, write val)
   221  	// arrange
   222  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   223  	mcp.mcpBehav.autoIODirOff = true
   224  	for bitState := 0; bitState <= 1; bitState++ {
   225  		adaptor.written = []byte{} // reset writes of Start() and former test
   226  		// arrange some values
   227  		testPort := "A"
   228  		testPin := uint8(7)
   229  		wantReg := uint8(0x14)          // OLATA
   230  		returnRead := uint8(0xFF)       // emulate bit is on
   231  		wantRegVal := returnRead & 0x7F // OLATA: bit 7 reset, all other untouched
   232  		if bitState == 1 {
   233  			returnRead = 0x7F              // emulate bit is off
   234  			wantRegVal = returnRead | 0x80 // OLATA: bit 7 set, all other untouched
   235  		}
   236  		// arrange reads
   237  		numCallsRead := 0
   238  		adaptor.i2cReadImpl = func(b []byte) (int, error) {
   239  			numCallsRead++
   240  			b[len(b)-1] = returnRead
   241  			return len(b), nil
   242  		}
   243  		// act
   244  		err := mcp.WriteGPIO(testPin, uint8(bitState), testPort)
   245  		// assert
   246  		gobottest.Assert(t, err, nil)
   247  		gobottest.Assert(t, len(adaptor.written), 3)
   248  		gobottest.Assert(t, adaptor.written[0], wantReg)
   249  		gobottest.Assert(t, adaptor.written[1], wantReg)
   250  		gobottest.Assert(t, adaptor.written[2], wantRegVal)
   251  		gobottest.Assert(t, numCallsRead, 1)
   252  	}
   253  }
   254  
   255  func TestMCP23017DriverCommandsWriteGPIOErrIODIR(t *testing.T) {
   256  	// arrange
   257  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   258  	adaptor.i2cWriteImpl = func([]byte) (int, error) {
   259  		return 0, errors.New("write error")
   260  	}
   261  	// act
   262  	err := mcp.WriteGPIO(7, 0, "A")
   263  	// assert
   264  	gobottest.Assert(t, err, errors.New("write error"))
   265  }
   266  
   267  func TestMCP23017DriverCommandsWriteGPIOErrOLAT(t *testing.T) {
   268  	// arrange
   269  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   270  	numCalls := 1
   271  	adaptor.i2cWriteImpl = func([]byte) (int, error) {
   272  		if numCalls == 2 {
   273  			return 0, errors.New("write error")
   274  		}
   275  		numCalls++
   276  		return 0, nil
   277  	}
   278  	// act
   279  	err := mcp.WriteGPIO(7, 0, "A")
   280  	// assert
   281  	gobottest.Assert(t, err, errors.New("write error"))
   282  }
   283  
   284  func TestMCP23017DriverReadGPIO(t *testing.T) {
   285  	// sequence to read:
   286  	// * read current state of IODIR (write reg, read val) => see also PinMode()
   287  	// * set IODIR of pin to input (manipulate val, write reg, write val) => see also PinMode()
   288  	// * read GPIO (write reg, read val)
   289  	// arrange
   290  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   291  	for bitState := 0; bitState <= 1; bitState++ {
   292  		adaptor.written = []byte{} // reset writes of Start() and former test
   293  		// arrange some values
   294  		testPort := "A"
   295  		testPin := uint8(7)
   296  		wantReg1 := uint8(0x00)             // IODIRA
   297  		wantReg2 := uint8(0x12)             // GPIOA
   298  		returnRead := []uint8{0x00, 0x7F}   // emulate all IO's are outputs, emulate bit is off
   299  		wantReg1Val := returnRead[0] | 0x80 // IODIRA: bit 7 set, all other untouched
   300  		if bitState == 1 {
   301  			returnRead[1] = 0xFF // emulate bit is set
   302  		}
   303  		// arrange reads
   304  		numCallsRead := 0
   305  		adaptor.i2cReadImpl = func(b []byte) (int, error) {
   306  			numCallsRead++
   307  			b[len(b)-1] = returnRead[numCallsRead-1]
   308  			return len(b), nil
   309  		}
   310  		// act
   311  		val, err := mcp.ReadGPIO(testPin, testPort)
   312  		// assert
   313  		gobottest.Assert(t, err, nil)
   314  		gobottest.Assert(t, numCallsRead, 2)
   315  		gobottest.Assert(t, len(adaptor.written), 4)
   316  		gobottest.Assert(t, adaptor.written[0], wantReg1)
   317  		gobottest.Assert(t, adaptor.written[1], wantReg1)
   318  		gobottest.Assert(t, adaptor.written[2], wantReg1Val)
   319  		gobottest.Assert(t, adaptor.written[3], wantReg2)
   320  		gobottest.Assert(t, val, uint8(bitState))
   321  	}
   322  }
   323  
   324  func TestMCP23017DriverReadGPIONoRefresh(t *testing.T) {
   325  	// sequence to read with take advantage of refresh optimization (see forceRefresh):
   326  	// * read current state of IODIR (write reg, read val) => by PinMode()
   327  	// * read GPIO (write reg, read val)
   328  	// arrange
   329  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   330  	for bitState := 0; bitState <= 1; bitState++ {
   331  		adaptor.written = []byte{} // reset writes of Start() and former test
   332  		// arrange some values
   333  		testPort := "A"
   334  		testPin := uint8(7)
   335  		wantReg1 := uint8(0x00)           // IODIRA
   336  		wantReg2 := uint8(0x12)           // GPIOA
   337  		returnRead := []uint8{0x80, 0x7F} // emulate all IO's are outputs except pin 7, emulate bit is off
   338  		if bitState == 1 {
   339  			returnRead[1] = 0xFF // emulate bit is set
   340  		}
   341  		// arrange reads
   342  		numCallsRead := 0
   343  		adaptor.i2cReadImpl = func(b []byte) (int, error) {
   344  			numCallsRead++
   345  			b[len(b)-1] = returnRead[numCallsRead-1]
   346  			return len(b), nil
   347  		}
   348  		// act
   349  		val, err := mcp.ReadGPIO(testPin, testPort)
   350  		// assert
   351  		gobottest.Assert(t, err, nil)
   352  		gobottest.Assert(t, numCallsRead, 2)
   353  		gobottest.Assert(t, len(adaptor.written), 2)
   354  		gobottest.Assert(t, adaptor.written[0], wantReg1)
   355  		gobottest.Assert(t, adaptor.written[1], wantReg2)
   356  		gobottest.Assert(t, val, uint8(bitState))
   357  	}
   358  }
   359  
   360  func TestMCP23017DriverReadGPIONoAutoDir(t *testing.T) {
   361  	// sequence to read with suppressed automatic setting of IODIR:
   362  	// * read GPIO (write reg, read val)
   363  	// arrange
   364  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   365  	mcp.mcpBehav.autoIODirOff = true
   366  	for bitState := 0; bitState <= 1; bitState++ {
   367  		adaptor.written = []byte{} // reset writes of Start() and former test
   368  		// arrange some values
   369  		testPort := "A"
   370  		testPin := uint8(7)
   371  		wantReg2 := uint8(0x12)   // GPIOA
   372  		returnRead := uint8(0x7F) // emulate bit is off
   373  		if bitState == 1 {
   374  			returnRead = 0xFF // emulate bit is set
   375  		}
   376  		// arrange reads
   377  		numCallsRead := 0
   378  		adaptor.i2cReadImpl = func(b []byte) (int, error) {
   379  			numCallsRead++
   380  			b[len(b)-1] = returnRead
   381  			return len(b), nil
   382  		}
   383  		// act
   384  		val, err := mcp.ReadGPIO(testPin, testPort)
   385  		// assert
   386  		gobottest.Assert(t, err, nil)
   387  		gobottest.Assert(t, numCallsRead, 1)
   388  		gobottest.Assert(t, len(adaptor.written), 1)
   389  		gobottest.Assert(t, adaptor.written[0], wantReg2)
   390  		gobottest.Assert(t, val, uint8(bitState))
   391  	}
   392  }
   393  
   394  func TestMCP23017DriverReadGPIOErr(t *testing.T) {
   395  	// arrange
   396  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   397  	// arrange reads
   398  	adaptor.i2cReadImpl = func(b []byte) (int, error) {
   399  		return len(b), errors.New("read error")
   400  	}
   401  	// act
   402  	_, err := mcp.ReadGPIO(7, "A")
   403  	// assert
   404  	gobottest.Assert(t, err, errors.New("read error"))
   405  }
   406  
   407  func TestMCP23017DriverPinMode(t *testing.T) {
   408  	// sequence for setting pin direction:
   409  	// * read current state of IODIR (write reg, read val)
   410  	// * set IODIR of pin to input or output (manipulate val, write reg, write val)
   411  	// TODO: can be optimized by not writing, when value is already fine
   412  	// arrange
   413  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   414  	for bitState := 0; bitState <= 1; bitState++ {
   415  		adaptor.written = []byte{} // reset writes of Start() and former test
   416  		// arrange some values
   417  		testPort := "A"
   418  		testPin := uint8(7)
   419  		wantReg := uint8(0x00)          // IODIRA
   420  		returnRead := uint8(0xFF)       // emulate all ports are inputs
   421  		wantRegVal := returnRead & 0x7F // bit 7 reset, all other untouched
   422  		if bitState == 1 {
   423  			returnRead = 0x00              // emulate all ports are outputs
   424  			wantRegVal = returnRead | 0x80 // bit 7 set, all other untouched
   425  		}
   426  		// arrange reads
   427  		numCallsRead := 0
   428  		adaptor.i2cReadImpl = func(b []byte) (int, error) {
   429  			numCallsRead++
   430  			b[len(b)-1] = returnRead
   431  			return len(b), nil
   432  		}
   433  		// act
   434  		err := mcp.PinMode(testPin, uint8(bitState), testPort)
   435  		// assert
   436  		gobottest.Assert(t, err, nil)
   437  		gobottest.Assert(t, len(adaptor.written), 3)
   438  		gobottest.Assert(t, adaptor.written[0], wantReg)
   439  		gobottest.Assert(t, adaptor.written[1], wantReg)
   440  		gobottest.Assert(t, adaptor.written[2], wantRegVal)
   441  		gobottest.Assert(t, numCallsRead, 1)
   442  	}
   443  }
   444  
   445  func TestMCP23017DriverPinModeErr(t *testing.T) {
   446  	// arrange
   447  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   448  	adaptor.i2cWriteImpl = func([]byte) (int, error) {
   449  		return 0, errors.New("write error")
   450  	}
   451  	// act
   452  	err := mcp.PinMode(7, 0, "A")
   453  	// assert
   454  	gobottest.Assert(t, err, errors.New("write error"))
   455  }
   456  
   457  func TestMCP23017DriverSetPullUp(t *testing.T) {
   458  	// sequence for setting input pin pull up:
   459  	// * read current state of GPPU (write reg, read val)
   460  	// * set GPPU of pin to target state (manipulate val, write reg, write val)
   461  	// TODO: can be optimized by not writing, when value is already fine
   462  	// arrange
   463  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   464  	for bitState := 0; bitState <= 1; bitState++ {
   465  		adaptor.written = []byte{} // reset writes of Start()
   466  		// arrange some values
   467  		testPort := "A"
   468  		wantReg := uint8(0x0C) // GPPUA
   469  		testPin := uint8(5)
   470  		returnRead := uint8(0xFF)       // emulate all I's with pull up
   471  		wantSetVal := returnRead & 0xDF // bit 5 cleared, all other unchanged
   472  		if bitState == 1 {
   473  			returnRead = uint8(0x00)       // emulate all I's without pull up
   474  			wantSetVal = returnRead | 0x20 // bit 5 set, all other unchanged
   475  		}
   476  		// arrange reads
   477  		numCallsRead := 0
   478  		adaptor.i2cReadImpl = func(b []byte) (int, error) {
   479  			numCallsRead++
   480  			b[len(b)-1] = returnRead
   481  			return len(b), nil
   482  		}
   483  		// act
   484  		err := mcp.SetPullUp(testPin, uint8(bitState), testPort)
   485  		// assert
   486  		gobottest.Assert(t, err, nil)
   487  		gobottest.Assert(t, len(adaptor.written), 3)
   488  		gobottest.Assert(t, adaptor.written[0], wantReg)
   489  		gobottest.Assert(t, adaptor.written[1], wantReg)
   490  		gobottest.Assert(t, adaptor.written[2], wantSetVal)
   491  		gobottest.Assert(t, numCallsRead, 1)
   492  	}
   493  }
   494  
   495  func TestMCP23017DriverSetPullUpErr(t *testing.T) {
   496  	// arrange
   497  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   498  	adaptor.i2cWriteImpl = func([]byte) (int, error) {
   499  		return 0, errors.New("write error")
   500  	}
   501  	// act
   502  	err := mcp.SetPullUp(7, 0, "A")
   503  	// assert
   504  	gobottest.Assert(t, err, errors.New("write error"))
   505  }
   506  
   507  func TestMCP23017DriverSetGPIOPolarity(t *testing.T) {
   508  	// sequence for setting input pin polarity:
   509  	// * read current state of IPOL (write reg, read val)
   510  	// * set IPOL of pin to target state (manipulate val, write reg, write val)
   511  	// TODO: can be optimized by not writing, when value is already fine
   512  	// arrange
   513  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   514  	for bitState := 0; bitState <= 1; bitState++ {
   515  		adaptor.written = []byte{} // reset writes of Start()
   516  		// arrange some values
   517  		testPort := "B"
   518  		wantReg := uint8(0x03) // IPOLB
   519  		testPin := uint8(6)
   520  		returnRead := uint8(0xFF)       // emulate all I's negotiated
   521  		wantSetVal := returnRead & 0xBF // bit 6 cleared, all other unchanged
   522  		if bitState == 1 {
   523  			returnRead = uint8(0x00)       // emulate all I's not negotiated
   524  			wantSetVal = returnRead | 0x40 // bit 6 set, all other unchanged
   525  		}
   526  		// arrange reads
   527  		numCallsRead := 0
   528  		adaptor.i2cReadImpl = func(b []byte) (int, error) {
   529  			numCallsRead++
   530  			b[len(b)-1] = returnRead
   531  			return len(b), nil
   532  		}
   533  		// act
   534  		err := mcp.SetGPIOPolarity(testPin, uint8(bitState), testPort)
   535  		// assert
   536  		gobottest.Assert(t, err, nil)
   537  		gobottest.Assert(t, len(adaptor.written), 3)
   538  		gobottest.Assert(t, adaptor.written[0], wantReg)
   539  		gobottest.Assert(t, adaptor.written[1], wantReg)
   540  		gobottest.Assert(t, adaptor.written[2], wantSetVal)
   541  		gobottest.Assert(t, numCallsRead, 1)
   542  	}
   543  }
   544  
   545  func TestMCP23017DriverSetGPIOPolarityErr(t *testing.T) {
   546  	// arrange
   547  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   548  	adaptor.i2cWriteImpl = func([]byte) (int, error) {
   549  		return 0, errors.New("write error")
   550  	}
   551  	// act
   552  	err := mcp.SetGPIOPolarity(7, 0, "A")
   553  	// assert
   554  	gobottest.Assert(t, err, errors.New("write error"))
   555  }
   556  
   557  func TestMCP23017DriverSetName(t *testing.T) {
   558  	d := initTestMCP23017Driver(0)
   559  	d.SetName("TESTME")
   560  	gobottest.Assert(t, d.Name(), "TESTME")
   561  }
   562  
   563  func TestMCP23017Driver_write(t *testing.T) {
   564  	// clear bit
   565  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   566  	port := mcp.getPort("A")
   567  	err := mcp.write(port.IODIR, uint8(7), 0)
   568  	gobottest.Assert(t, err, nil)
   569  
   570  	// set bit
   571  	mcp, adaptor = initTestMCP23017DriverWithStubbedAdaptor(0)
   572  	port = mcp.getPort("B")
   573  	err = mcp.write(port.IODIR, uint8(7), 1)
   574  	gobottest.Assert(t, err, nil)
   575  
   576  	// write error
   577  	mcp, adaptor = initTestMCP23017DriverWithStubbedAdaptor(0)
   578  	adaptor.i2cWriteImpl = func([]byte) (int, error) {
   579  		return 0, errors.New("write error")
   580  	}
   581  	err = mcp.write(port.IODIR, uint8(7), 0)
   582  	gobottest.Assert(t, err, errors.New("write error"))
   583  
   584  	// read error
   585  	mcp, adaptor = initTestMCP23017DriverWithStubbedAdaptor(0)
   586  	adaptor.i2cReadImpl = func(b []byte) (int, error) {
   587  		return len(b), errors.New("read error")
   588  	}
   589  	err = mcp.write(port.IODIR, uint8(7), 0)
   590  	gobottest.Assert(t, err, errors.New("read error"))
   591  	adaptor.i2cReadImpl = func(b []byte) (int, error) {
   592  		return len(b), nil
   593  	}
   594  	err = mcp.write(port.IODIR, uint8(7), 1)
   595  	gobottest.Assert(t, err, nil)
   596  }
   597  
   598  func TestMCP23017Driver_read(t *testing.T) {
   599  	// read
   600  	mcp, adaptor := initTestMCP23017DriverWithStubbedAdaptor(0)
   601  	port := mcp.getPort("A")
   602  	adaptor.i2cReadImpl = func(b []byte) (int, error) {
   603  		copy(b, []byte{255})
   604  		return 1, nil
   605  	}
   606  	val, _ := mcp.read(port.IODIR)
   607  	gobottest.Assert(t, val, uint8(255))
   608  
   609  	// read error
   610  	mcp, adaptor = initTestMCP23017DriverWithStubbedAdaptor(0)
   611  	adaptor.i2cReadImpl = func(b []byte) (int, error) {
   612  		return len(b), errors.New("read error")
   613  	}
   614  
   615  	val, err := mcp.read(port.IODIR)
   616  	gobottest.Assert(t, val, uint8(0))
   617  	gobottest.Assert(t, err, errors.New("read error"))
   618  
   619  	// read
   620  	mcp, adaptor = initTestMCP23017DriverWithStubbedAdaptor(0)
   621  	port = mcp.getPort("A")
   622  	adaptor.i2cReadImpl = func(b []byte) (int, error) {
   623  		copy(b, []byte{255})
   624  		return 1, nil
   625  	}
   626  	val, _ = mcp.read(port.IODIR)
   627  	gobottest.Assert(t, val, uint8(255))
   628  }
   629  
   630  func TestMCP23017Driver_getPort(t *testing.T) {
   631  	// port A
   632  	mcp := initTestMCP23017Driver(0)
   633  	expectedPort := getBank(0).portA
   634  	actualPort := mcp.getPort("A")
   635  	gobottest.Assert(t, expectedPort, actualPort)
   636  
   637  	// port B
   638  	mcp = initTestMCP23017Driver(0)
   639  	expectedPort = getBank(0).portB
   640  	actualPort = mcp.getPort("B")
   641  	gobottest.Assert(t, expectedPort, actualPort)
   642  
   643  	// default
   644  	mcp = initTestMCP23017Driver(0)
   645  	expectedPort = getBank(0).portA
   646  	actualPort = mcp.getPort("")
   647  	gobottest.Assert(t, expectedPort, actualPort)
   648  
   649  	// port A bank 1
   650  	mcp = initTestMCP23017Driver(1)
   651  	expectedPort = getBank(1).portA
   652  	actualPort = mcp.getPort("")
   653  	gobottest.Assert(t, expectedPort, actualPort)
   654  }