Skip to content

Commit

Permalink
Move tests from package to smoketest.
Browse files Browse the repository at this point in the history
  • Loading branch information
gsexton committed Sep 2, 2024
1 parent 0fe4bbe commit 2cb6dd0
Show file tree
Hide file tree
Showing 5 changed files with 625 additions and 67 deletions.
133 changes: 68 additions & 65 deletions periph-smoketest/gpiosmoketest/README.md
Original file line number Diff line number Diff line change
@@ -1,70 +1,73 @@
# 'gpio' smoke test
# Testing

Verifies that the library physically work. It requires the user to connect two
GPIO pins together and provide their pin number at the command line.
The tests implemented perform *functional* tests of the library. This means that
the tests performed interact with a GPIO chipset, and perform actual read/write
operations. Using this test set, it's possible to quickly and accurately check
if the library is working as expected on a specific hardware/kernel combination.

Example output running on a Raspberry Pi:
## Requirements

Although the library is not Raspberry Pi specific, the GPIO pin names used for
tests are.

As written, the tests must be executed on a Raspberry Pi SBC running Linux. Tested
models are:

* Raspberry Pi 3B
* Raspberry Pi Zero W
* Raspberry Pi 4
* Raspberry Pi 5

You must also have the golang SDK installed.

## Setting Up

In order to execute the functional tests, you must jumper the sets of pins shown
below together.

For example, the single line tests require GPIO5 and GPIO13 to be connected to
each other, so a jumper is required between pins 29 and 33. For the multi-line
tests to work, you must connect the following GPIO pins together with jumpers.

| GPIO Output | Output Pin # | GPIO Input | Input Pin # |
| ----------- | ------------ | ---------- | ----------- |
| GPIO2 | 3 | GPIO10 | 19 |
| GPIO3 | 5 | GPIO11 | 23 |
| GPIO4 | 7 | GPIO12 | 32 |
| GPIO5 | 29 | GPIO13 | 33 |
| GPIO6 | 31 | GPIO14 | 8 |
| GPIO7 | 26 | GPIO15 | 10 |
| GPIO8 | 24 | GPIO16 | 36 |
| GPIO9 | 21 | GPIO17 | 11 |

## Cross-Compiling
If you don't have a working go installation on the target machine, you can cross
compile from one machine and then copy the test binary to the target machine.

To cross compile for Raspberry Pi, execute the command:

```bash
$periph.io/x/host/gpioctl> GOOS=linux GOARCH=arm64 go test -c
$periph.io/x/host/gpioctl> scp gpioioctl.test [email protected]:~
$periph.io/x/host/gpioctl> ssh [email protected]
$user> ./gpioioctl.test -test.v
```
for Pi Zero W, use:

```bash
$periph.io/x/host/gpioctl> GOOS=linux GOARCH=arm GOARM=6 go test -c
$periph.io/x/host/gpioctl> scp gpioioctl.test [email protected]:~
$periph.io/x/host/gpioctl> ssh [email protected]
$user> ./gpioioctl.test -test.v

```
$ gpio-test 12 6
Using drivers:
- bcm283x
- rpi
- sysfs-gpio
- sysfs-spi
- sysfs-i2c
Using pins and their current state:
- GPIO12: In/High
- GPIO6: In/High
Testing GPIO6 -> GPIO12
Testing base functionality
GPIO12.In(Float)
GPIO6.Out(Low)
-> GPIO12: In/Low
-> GPIO6: Out/Low
GPIO6.Out(High)
-> GPIO12: In/High
-> GPIO6: Out/High
Testing edges
GPIO12.Edges()
GPIO6.Out(Low)
Low <- GPIO12
GPIO6.Out(High)
High <- GPIO12
GPIO6.Out(Low)
Low <- GPIO12
GPIO12.DisableEdges()
Testing pull resistor
GPIO6.In(Down)
-> GPIO12: In/Low
-> GPIO6: In/Low
GPIO6.In(Up)
-> GPIO12: In/High
-> GPIO6: In/High
Testing GPIO12 -> GPIO6
Testing base functionality
GPIO6.In(Float)
GPIO12.Out(Low)
-> GPIO6: In/Low
-> GPIO12: Out/Low
GPIO12.Out(High)
-> GPIO6: In/High
-> GPIO12: Out/High
Testing edges
GPIO6.Edges()
GPIO12.Out(Low)
Low <- GPIO6
GPIO12.Out(High)
High <- GPIO6
GPIO12.Out(Low)
Low <- GPIO6
GPIO6.DisableEdges()
Testing pull resistor
GPIO12.In(Down)
-> GPIO6: In/Low
-> GPIO12: In/Low
GPIO12.In(Up)
-> GPIO6: In/High
-> GPIO12: In/High

## Executing the Tests

After connecting the jumper wires as shown above, and you have golang installed
and the go/bin directory in the path, change to this directory and execute the
command:

```bash
$> go test -v -cover
```
163 changes: 163 additions & 0 deletions periph-smoketest/gpiosmoketest/gpio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package gpiosmoketest

// Copyright 2024 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
//
// The In/Out tests depend upon having a jumper wire connecting _OUT_LINE and
// _IN_LINE

import (
"time"

"periph.io/x/conn/v3/driver/driverreg"
"periph.io/x/conn/v3/gpio"
"periph.io/x/host/v3/gpioioctl"

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / lint: ubuntu-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / lint: ubuntu-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / lint: ubuntu-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / test: ubuntu-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / lint: macos-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / lint: macos-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / lint: macos-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / test: macos-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / test: windows-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / lint: windows-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / lint: windows-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:

Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go

View workflow job for this annotation

GitHub Actions / lint: windows-latest

no required module provides package periph.io/x/host/v3/gpioioctl; to add it:
)

const (
_OUT_LINE = "GPIO5"
_IN_LINE = "GPIO13"
)

func init() {
_, _ = driverreg.Init()
}

func TestWriteReadSinglePin(t *SmokeTestT) {
var err error
chip := gpioioctl.Chips[0]
inLine := chip.ByName(_IN_LINE)
outLine := chip.ByName(_OUT_LINE)
defer inLine.Close()
defer outLine.Close()
err = outLine.Out(true)
if err != nil {
t.Errorf("outLine.Out() %s", err)
}
if val := inLine.Read(); !val {
t.Error("Error reading/writing GPIO Pin. Expected true, received false!")
}
if inLine.Pull() != gpio.PullUp {
t.Errorf("Pull() returned %s expected %s", gpioioctl.PullLabels[inLine.Pull()], gpioioctl.PullLabels[gpio.PullUp])
}
err = outLine.Out(false)
if err != nil {
t.Errorf("outLine.Out() %s", err)
}
if val := inLine.Read(); val {
t.Error("Error reading/writing GPIO Pin. Expected false, received true!")
}
/*
By Design, lines should auto change directions if Read()/Out() are called
and they don't match.
*/
err = inLine.Out(false)
if err != nil {
t.Errorf("inLine.Out() %s", err)
}
time.Sleep(500 * time.Millisecond)
err = inLine.Out(true)
if err != nil {
t.Errorf("inLine.Out() %s", err)
}
if val := outLine.Read(); !val {
t.Error("Error read/writing with auto-reverse of line functions.")
}
err = inLine.Out(false)
if err != nil {
t.Errorf("TestWriteReadSinglePin() %s", err)
}
if val := outLine.Read(); val {
t.Error("Error read/writing with auto-reverse of line functions.")
}

}

func clearEdges(line gpio.PinIn) bool {
result := false
for line.WaitForEdge(10 * time.Millisecond) {
result = true
}
return result
}

func TestWaitForEdgeTimeout(t *SmokeTestT) {
line := gpioioctl.Chips[0].ByName(_IN_LINE)
defer line.Close()
err := line.In(gpio.PullUp, gpio.BothEdges)
if err != nil {
t.Error(err)
}
clearEdges(line)
tStart := time.Now().UnixMilli()
line.WaitForEdge(5 * time.Second)
tEnd := time.Now().UnixMilli()
tDiff := tEnd - tStart
if tDiff < 4500 || tDiff > 5500 {
t.Errorf("timeout duration failure. Expected duration: 5000, Actual duration: %d", tDiff)
}
}

// Test detection of rising, falling, and both.
func TestWaitForEdgeSinglePin(t *SmokeTestT) {
tests := []struct {
startVal gpio.Level
edge gpio.Edge
writeVal gpio.Level
}{
{startVal: false, edge: gpio.RisingEdge, writeVal: true},
{startVal: true, edge: gpio.FallingEdge, writeVal: false},
{startVal: false, edge: gpio.BothEdges, writeVal: true},
{startVal: true, edge: gpio.BothEdges, writeVal: false},
}
var err error
line := gpioioctl.Chips[0].ByName(_IN_LINE)
outLine := gpioioctl.Chips[0].ByName(_OUT_LINE)
defer line.Close()
defer outLine.Close()

for _, test := range tests {
err = outLine.Out(test.startVal)
if err != nil {
t.Errorf("set initial value. %s", err)
}
err = line.In(gpio.PullUp, test.edge)
if err != nil {
t.Errorf("line.In() %s", err)
}
clearEdges(line)
err = outLine.Out(test.writeVal)
if err != nil {
t.Errorf("outLine.Out() %s", err)
}
if edgeReceived := line.WaitForEdge(time.Second); !edgeReceived {
t.Errorf("Expected Edge %s was not received on transition from %t to %t", gpioioctl.EdgeLabels[test.edge], test.startVal, test.writeVal)
}
}
}

func TestHalt(t *SmokeTestT) {
line := gpioioctl.Chips[0].ByName(_IN_LINE)
defer line.Close()
err := line.In(gpio.PullUp, gpio.BothEdges)
if err != nil {
t.Fatalf("TestHalt() %s", err)
}
clearEdges(line)
// So what we'll do here is setup a goroutine to wait three seconds and then send a halt.
go func() {
time.Sleep(time.Second * 3)
err = line.Halt()
if err != nil {
t.Error(err)
}
}()
tStart := time.Now().UnixMilli()
line.WaitForEdge(time.Second * 30)
tEnd := time.Now().UnixMilli()
tDiff := tEnd - tStart
if tDiff > 3500 {
t.Errorf("error calling halt to interrupt WaitForEdge() Duration %d exceeded expected value.", tDiff)
}
}
77 changes: 77 additions & 0 deletions periph-smoketest/gpiosmoketest/gpioioctlsmoketest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2025 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

// Package gpioioctlsmoketest is leveraged by periph-smoketest to verify that basic
// GPIO pin functionality works.
package gpiosmoketest

import (
"errors"
"flag"
"log"
)

type SmokeTestT struct {
error bool
fatal bool
}

func (st *SmokeTestT) Error(args ...any) {
st.error = true
log.Println(args...)
}
func (st *SmokeTestT) Errorf(format string, args ...any) {
st.error = true
log.Printf(format, args...)
}
func (st *SmokeTestT) Fatal(args ...any) {
st.fatal = true
log.Fatal(args...)
}
func (st *SmokeTestT) Fatalf(format string, args ...any) {
st.fatal = true
log.Fatalf(format, args...)
}
func (st *SmokeTestT) Log(args ...any) {
log.Println(args...)
}
func (st *SmokeTestT) Logf(format string, args ...any) {
log.Printf(format, args...)
}
func (st *SmokeTestT) ErrorsOrFatals() bool {
return st.error || st.fatal
}

type SmokeTest struct {
}

// Name implements periph-smoketest.SmokeTest.
func (s *SmokeTest) Name() string {
return "gpio"
}

// Description implements periph-smoketest.SmokeTest.
func (s *SmokeTest) Description() string {
return "Tests basic functionality, edge detection and input pull resistors"
}

// Run implements periph-smoketest.SmokeTest.
func (s *SmokeTest) Run(f *flag.FlagSet, args []string) error {
st := &SmokeTestT{}
TestWriteReadSinglePin(st)
TestWaitForEdgeTimeout(st)
TestWaitForEdgeSinglePin(st)
TestHalt(st)
TestLineSetCreation(st)
TestLineSetReadWrite(st)
TestLineSetWaitForEdgeTimeout(st)
TestLineSetHalt(st)
TestLineSetWaitForEdge(st)
TestLineSetConfigWithOverride(st)
if st.ErrorsOrFatals() {
return errors.New("Smoketest failure.")
}
return nil
}

Loading

0 comments on commit 2cb6dd0

Please sign in to comment.