// Code generated by mockery v1.0.0. DO NOT EDIT.
package main
import mock "github.com/stretchr/testify/mock"
// mockExecuter is an autogenerated mock type for the executer type
type mockExecuter struct {
mock.Mock
}
// covertOutputToCoverage provides a mock function with given fields: termOutput
func (_m *mockExecuter) covertOutputToCoverage(termOutput string) ([]testLine, error) {
ret := _m.Called(termOutput)
var r0 []testLine
if rf, ok := ret.Get(0).(func(string) []testLine); ok {
r0 = rf(termOutput)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]testLine)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(termOutput)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// runGoTest provides a mock function with given fields:
func (_m *mockExecuter) runGoTest() (string, error) {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// validateTestOutput provides a mock function with given fields: tl, o
func (_m *mockExecuter) validateTestOutput(tl []testLine, o string) error {
ret := _m.Called(tl, o)
var r0 error
if rf, ok := ret.Get(0).(func([]testLine, string) error); ok {
r0 = rf(tl, o)
} else {
r0 = ret.Error(0)
}
return r0
}
// Package main contains code to do with ensuring coverage is over 80%
package main
import (
"fmt"
"log"
"os/exec"
"strconv"
"strings"
)
const (
minPercentCov = 75.0
coverageStringNotFound = -1.0
firstItemIndex = 1
floatByteSize = 64
emptySliceLen = 0
lenOfPercentChar = 1
indexOfEmptyLine = 1
)
var excludedPkgs = map[string]bool{
"github.com/gowhale/led-matrix-golang": true,
"golang-repo-template": true,
"golang-repo-template/pkg/fruit": true,
"github.com/gowhale/led-matrix-golang/cmd/possible-letters": true,
}
func main() {
if err := run(&execute{}); err != nil {
log.Fatalln(err)
}
log.Println("Tests=PASS Coverage=PASS")
}
//go:generate go run github.com/vektra/mockery/cmd/mockery -name executer -inpkg --filename executer_mock.go
type executer interface {
runGoTest() (string, error)
covertOutputToCoverage(termOutput string) ([]testLine, error)
validateTestOutput(tl []testLine, o string) error
}
func run(e executer) error {
output, err := e.runGoTest()
if err != nil {
log.Println(output)
return err
}
tl, err := e.covertOutputToCoverage(output)
if err != nil {
return err
}
return e.validateTestOutput(tl, output)
}
var execCommand = exec.Command
type execute struct{}
func (*execute) runGoTest() (string, error) {
cmd := execCommand("go", "test", "./...", "--cover")
output, err := cmd.CombinedOutput()
termOutput := string(output)
return termOutput, err
}
type testLine struct {
pkgName string
coverage float64
coverLine bool
}
func getCoverage(line string) (testLine, error) {
if !strings.Contains(line, "go: downloading") {
pkgName := strings.Fields(line)[firstItemIndex]
if _, ok := excludedPkgs[pkgName]; !ok {
coverageIndex := strings.Index(line, "coverage: ")
if coverageIndex != coverageStringNotFound {
lineFields := strings.Fields(line[coverageIndex:])
pkgPercentStr := lineFields[firstItemIndex][:len(lineFields[firstItemIndex])-lenOfPercentChar]
pkgPercentFloat, err := strconv.ParseFloat(pkgPercentStr, floatByteSize)
if err != nil {
return testLine{}, err
}
log.Println(pkgPercentStr)
return testLine{pkgName: pkgName, coverage: pkgPercentFloat, coverLine: true}, nil
}
return testLine{pkgName: pkgName, coverage: coverageStringNotFound, coverLine: true}, nil
}
}
return testLine{coverLine: false}, nil
}
func (*execute) covertOutputToCoverage(termOutput string) ([]testLine, error) {
testStruct := []testLine{}
lines := strings.Split(termOutput, "\n")
for _, line := range lines[:len(lines)-indexOfEmptyLine] {
tl, err := getCoverage(line)
if err != nil {
return nil, err
}
if tl.coverLine {
testStruct = append(testStruct, tl)
}
}
return testStruct, nil
}
func (*execute) validateTestOutput(tl []testLine, o string) error {
invalidOutputs := []string{}
for _, line := range tl {
switch {
case !line.coverLine:
invalidOutputs = append(invalidOutputs, fmt.Sprintf("pkg=%s is missing tests", line.pkgName))
case line.coverage < minPercentCov:
invalidOutputs = append(invalidOutputs, fmt.Sprintf("pkg=%s cov=%f under the %f%% minimum line coverage", line.pkgName, line.coverage, minPercentCov))
}
}
if len(invalidOutputs) == emptySliceLen {
return nil
}
log.Println(o)
log.Println("###############################")
log.Println("invalid pkg's:")
for i, invalid := range invalidOutputs {
log.Printf("id=%d problem=%s", i, invalid)
}
log.Println("###############################")
return fmt.Errorf("the following pkgs are not valid: %+v", invalidOutputs)
}
// Package config is responsible for loading GPIO configurations
package config
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
)
// PinConfig is used to configure GPIO pins to rows / columns
// This is based on the assumption that the Pi is connection to a LED matrix
// Example matrix below
// https://www.jameco.com/Jameco/workshop/learning-center/electronic-fundamentals-working-with-led-dot-matrix-displays-fig3.jpg
type PinConfig struct {
RowPins []int `json:"rows"`
ColPins []int `json:"cols"`
}
// LoadConfig will load a json file and return PinConfig
func LoadConfig(filename string) (PinConfig, error) {
return loadConfigImpl(&readJSON{}, filename)
}
func loadConfigImpl(jReader readerJSON, filename string) (PinConfig, error) {
file, err := jReader.ReadFile(fmt.Sprintf("config-files/%s", filename))
if err != nil {
return PinConfig{}, err
}
config := PinConfig{}
err = json.Unmarshal([]byte(file), &config)
if err != nil {
return PinConfig{}, err
}
log.Printf("row pins = %+v", config.RowPins)
log.Printf("col pins = %+v", config.ColPins)
return config, nil
}
// RowCount returns the amount of rows specified in a Config file
func (cfg *PinConfig) RowCount() int {
return len(cfg.RowPins)
}
// ColCount returns the amount of cols specified in a Config file
func (cfg *PinConfig) ColCount() int {
return len(cfg.ColPins)
}
type readJSON struct{}
//go:generate go run github.com/vektra/mockery/cmd/mockery -name readerJSON -inpkg --filename read_json_mock.go
type readerJSON interface {
ReadFile(filename string) ([]byte, error)
}
func (*readJSON) ReadFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
}
// Code generated by mockery v1.0.0. DO NOT EDIT.
package config
import mock "github.com/stretchr/testify/mock"
// mockReaderJSON is an autogenerated mock type for the readerJSON type
type mockReaderJSON struct {
mock.Mock
}
// ReadFile provides a mock function with given fields: filename
func (_m *mockReaderJSON) ReadFile(filename string) ([]byte, error) {
ret := _m.Called(filename)
var r0 []byte
if rf, ok := ret.Get(0).(func(string) []byte); ok {
r0 = rf(filename)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(filename)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Package gui is responsible for visual output
// File led.go implements LED methods for the raspberry pi
package gui
import (
"log"
"time"
"github.com/gowhale/led-matrix-golang/pkg/config"
)
const (
sleep = 1 // amount of ms to keep single LED on whilst multiplexing
cordXIndex = 0
cordYIndex = 1
)
type guiLED struct {
rowCount, colCount int
rowPins, colPins []int
rpioController rpioProcessor
}
// NewledGUI returns ledGUI struct to display output on terminal
func NewledGUI(cfg config.PinConfig) (Screen, error) {
return newledGUIImpl(cfg, &rpioProc{})
}
func newledGUIImpl(cfg config.PinConfig, rp rpioProcessor) (Screen, error) {
log.Println("Creating LED GUI. Opening gpio")
err := rp.Open()
if err != nil {
return nil, err
}
log.Println("Setting all pins to low")
for _, c := range cfg.ColPins {
p := rp.Pin(c)
p.Output()
p.Low()
}
for _, c := range cfg.RowPins {
p := rp.Pin(c)
p.Output()
p.Low()
}
return &guiLED{
rowCount: cfg.RowCount(),
colCount: cfg.ColCount(),
rowPins: cfg.RowPins,
colPins: cfg.ColPins,
rpioController: rp,
}, nil
}
func (l *guiLED) setRowPinLow(rowPin int) {
for _, r := range l.rowPins {
p := l.rpioController.Pin(r)
p.High()
}
p := l.rpioController.Pin(rowPin)
p.Low()
}
func (l *guiLED) setColPinHigh(col int) {
p := l.rpioController.Pin(col)
p.High()
time.Sleep(time.Microsecond * sleep)
p.Low()
}
// CordinatesToLED lights up a matrix's light at specified coordinate
// Only lights temporarily used for multiplexing the lights
func (l *guiLED) CordinatesToLED(cord coordinate) error {
l.setRowPinLow(l.rowPins[cord[cordYIndex]])
l.setColPinHigh(l.colPins[cord[cordXIndex]])
return nil
}
func letterToLED(l [][]int) []coordinate {
coordinates := []coordinate{}
for i, row := range l {
for j, col := range row {
if col == LEDOn {
coordinates = append(coordinates, coordinate{j, i})
}
}
}
return coordinates
}
// AllLEDSOff clears the termina
func (*guiLED) AllLEDSOff() error {
return nil
}
func displayLEDMatrix(matrix [][]int, t time.Duration, l Screen) error {
startTime := time.Now()
coordinates := letterToLED(matrix)
for time.Since(startTime) < t {
for _, c := range coordinates {
if err := l.CordinatesToLED(c); err != nil {
return err
}
}
}
return nil
}
// DisplayMatrix displays the matrix provided
func (l *guiLED) DisplayMatrix(matrix [][]int, t time.Duration) error {
return displayLEDMatrix(matrix, t, l)
}
// Close turns all the gpio pins to low and closes connection
func (l *guiLED) Close() error {
allPins := append(l.rowPins, l.colPins...)
for _, p := range allPins {
l.rpioController.Pin(p).Low()
}
return l.rpioController.Close()
}
// Code generated by mockery v1.0.0. DO NOT EDIT.
package gui
import mock "github.com/stretchr/testify/mock"
// mockPinProcessor is an autogenerated mock type for the pinProcessor type
type mockPinProcessor struct {
mock.Mock
}
// High provides a mock function with given fields:
func (_m *mockPinProcessor) High() {
_m.Called()
}
// Low provides a mock function with given fields:
func (_m *mockPinProcessor) Low() {
_m.Called()
}
// Output provides a mock function with given fields:
func (_m *mockPinProcessor) Output() {
_m.Called()
}
package gui
import "github.com/stianeikeland/go-rpio"
type rpioProc struct{}
//go:generate go run github.com/vektra/mockery/cmd/mockery -name rpioProcessor -inpkg --filename rpio_processor_mock.go
type rpioProcessor interface {
Open() (err error)
Close() (err error)
Pin(p int) pinProcessor
}
func (*rpioProc) Open() (err error) {
return rpio.Open()
}
func (*rpioProc) Close() (err error) {
return rpio.Close()
}
func (*rpioProc) Pin(p int) pinProcessor {
return rpio.Pin(p)
}
//go:generate go run github.com/vektra/mockery/cmd/mockery -name pinProcessor -inpkg --filename pin_processor_mock.go
type pinProcessor interface {
Output()
Low()
High()
}
// Code generated by mockery v1.0.0. DO NOT EDIT.
package gui
import mock "github.com/stretchr/testify/mock"
// mockRpioProcessor is an autogenerated mock type for the rpioProcessor type
type mockRpioProcessor struct {
mock.Mock
}
// Close provides a mock function with given fields:
func (_m *mockRpioProcessor) Close() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Open provides a mock function with given fields:
func (_m *mockRpioProcessor) Open() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Pin provides a mock function with given fields: p
func (_m *mockRpioProcessor) Pin(p int) pinProcessor {
ret := _m.Called(p)
var r0 pinProcessor
if rf, ok := ret.Get(0).(func(int) pinProcessor); ok {
r0 = rf(p)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(pinProcessor)
}
}
return r0
}
// Code generated by mockery v1.0.0. DO NOT EDIT.
package gui
import (
time "time"
mock "github.com/stretchr/testify/mock"
)
// MockScreen is an autogenerated mock type for the Screen type
type MockScreen struct {
mock.Mock
}
// AllLEDSOff provides a mock function with given fields:
func (_m *MockScreen) AllLEDSOff() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Close provides a mock function with given fields:
func (_m *MockScreen) Close() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// CordinatesToLED provides a mock function with given fields: _a0
func (_m *MockScreen) CordinatesToLED(_a0 coordinate) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(coordinate) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// DisplayMatrix provides a mock function with given fields: matrix, displayDuration
func (_m *MockScreen) DisplayMatrix(matrix [][]int, displayDuration time.Duration) error {
ret := _m.Called(matrix, displayDuration)
var r0 error
if rf, ok := ret.Get(0).(func([][]int, time.Duration) error); ok {
r0 = rf(matrix, displayDuration)
} else {
r0 = ret.Error(0)
}
return r0
}
// Package gui is responsible for visual output
// File terminal.go implements terminal methods for gui interface
package gui
import (
"os"
"os/exec"
"time"
"github.com/gowhale/led-matrix-golang/pkg/config"
)
type terminalGui struct {
colCount, rowCount int
to terminalOutputter
}
// NewTerminalGui returns terminalGui struct to display output on terminal
func NewTerminalGui(cfg config.PinConfig) Screen {
return &terminalGui{
rowCount: len(cfg.RowPins),
colCount: len(cfg.ColPins),
to: &terminalOutput{},
}
}
var execCommand = exec.Command
// AllLEDSOff clears the terminal
func (*terminalGui) AllLEDSOff() error {
cmd := execCommand("clear")
cmd.Stdout = os.Stdout
return cmd.Run()
}
// DisplayMatrix displays the matrix provided
func (*terminalGui) DisplayMatrix(matrix [][]int, duration time.Duration) error {
return displayTerminalMatrixImpl(&terminalOutput{}, matrix, duration)
}
func (*terminalGui) Close() error {
return nil
}
// DisplayMatrix displays the matrix provided
func displayTerminalMatrixImpl(t terminalOutputter, matrix [][]int, duration time.Duration) error {
count := rowColStartIndex
for _, row := range matrix {
for _, col := range row {
if err := lightLED(t, col); err != nil {
return err
}
count++
}
if err := t.Printf("\n"); err != nil {
return err
}
}
time.Sleep(duration)
return nil
}
func lightLED(t terminalOutputter, col int) error {
if col == LEDOn {
return t.Printf("0")
}
return t.Printf(" ")
}
func printRow(t *terminalGui, x, y, count int, cords coordinate) error {
if count%t.colCount == 0 && count > 0 {
if err := t.to.Printf("#\n#"); err != nil {
return err
}
}
if x == cords[cordXIndex] && y == cords[cordYIndex] {
if err := t.to.Printf("0"); err != nil {
return err
}
}
return t.to.Printf(" ")
}
func (t *terminalGui) CordinatesToLED(cords coordinate) error {
if err := t.AllLEDSOff(); err != nil {
return err
}
if err := t.to.Printf("cord: x=%d y=%d\n##########\n#", cords[cordXIndex], cords[cordYIndex]); err != nil {
return err
}
count := 0
for x := 0; x < t.colCount; x++ {
for y := 0; y < t.rowCount; y++ {
if err := printRow(t, x, y, count, cords); err != nil {
return err
}
count++
}
}
return t.to.Printf("#\n##########")
}
package gui
import "fmt"
//go:generate go run github.com/vektra/mockery/cmd/mockery -name terminalOutputter -inpkg --filename terminal_outputter_mock.go
type terminalOutputter interface {
Printf(format string, a ...interface{}) error
}
type terminalOutput struct{}
func (*terminalOutput) Printf(format string, a ...interface{}) error {
_, err := fmt.Printf(format, a...)
return err
}
// Code generated by mockery v1.0.0. DO NOT EDIT.
package gui
import mock "github.com/stretchr/testify/mock"
// mockTerminalOutputter is an autogenerated mock type for the terminalOutputter type
type mockTerminalOutputter struct {
mock.Mock
}
// Printf provides a mock function with given fields: format, a
func (_m *mockTerminalOutputter) Printf(format string, a ...interface{}) error {
var _ca []interface{}
_ca = append(_ca, format)
_ca = append(_ca, a...)
ret := _m.Called(_ca...)
var r0 error
if rf, ok := ret.Get(0).(func(string, ...interface{}) error); ok {
r0 = rf(format, a...)
} else {
r0 = ret.Error(0)
}
return r0
}
package matrix
import (
"fmt"
"github.com/gowhale/led-matrix-golang/pkg/config"
"github.com/gowhale/led-matrix-golang/pkg/gui"
)
const (
// OffsetStart is the start val for offsetting
OffsetStart = 0
)
// TrimMatrix will take in a matrix then trim it according to the amount of rows, cols and offset
func TrimMatrix(matrix [][]int, cfg config.PinConfig, offset int) ([][]int, error) {
newMatrix := make([][]int, cfg.RowCount())
for i := range newMatrix {
newMatrix[i] = make([]int, cfg.ColCount())
}
for i := range matrix {
count := OffsetStart
currentOffset := offset + count
for (count < len(matrix[i])) && ((offset - currentOffset) < cfg.ColCount()) && currentOffset < len(matrix[i]) && count < cfg.ColCount() {
newMatrix[i][count] = matrix[i][currentOffset]
currentOffset++
count++
}
}
for i := range newMatrix {
for len(newMatrix[i]) < cfg.ColCount() {
newMatrix[i] = append(newMatrix[i], gui.LEDOff)
}
}
return newMatrix, nil
}
// ConcatanateLetters will create a matrix
func ConcatanateLetters(word string) ([][]int, error) {
sentenceAsLEDs := [][]int{}
for index, l := range word {
newLetter, ok := gui.LetterMap[string(l)]
if !ok {
return nil, fmt.Errorf("l=%s not in LetterMap", string(l))
}
if index == OffsetStart {
sentenceAsLEDs = append([][]int{}, newLetter...)
}
if index != OffsetStart {
for j, r := range newLetter {
sentenceAsLEDs[j] = append(sentenceAsLEDs[j], gui.LEDOff)
sentenceAsLEDs[j] = append(sentenceAsLEDs[j], r...)
}
}
}
return sentenceAsLEDs, nil
}