Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

periph-smoketest: Move tests from package to smoketest. #7

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
```
182 changes: 182 additions & 0 deletions periph-smoketest/gpiosmoketest/gpio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
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"
)

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

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

// Test the consumer field. Since this actually configures a line for output,
// it actually tests a fair amount of the code to request a line, and configure
// it.
func TestConsumer(t *SmokeTestT) {
l := gpioioctl.Chips[0].ByName(_OUT_LINE)
if l == nil {
t.Fatalf("Error retrieving GPIO Line %s", _OUT_LINE)
}
defer l.Close()
// Consumer isn't written until the line is configured.
err := l.Out(true)
if err != nil {
t.Errorf("l.Out() %s", err)
}
if len(l.Consumer()) == 0 {
t.Error("No consumer name found.")
}
}

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)
}
}
75 changes: 75 additions & 0 deletions periph-smoketest/gpiosmoketest/gpioioctlsmoketest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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.
//
// Functional smoke test for the host/gpioioctl package.
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