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

WIP Add test infrastructure #1091

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
33 changes: 33 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Testing

on: [push, pull_request]

jobs:
linux-test:
runs-on: ubuntu-latest
container:
image: tfcollins/libiio_ubuntu_22_04-ci:latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install dependencies
run: |
apt update
apt install -y lcov
- name: Build and Test
run: |
mkdir build
cd build
cmake .. -DTESTS=ON -DTESTS_DEBUG=ON -DTESTS_COVERAGE=ON -DWITH_GCOV=ON
make
make coverage

# - name: Upload coverage to Codecov
# uses: codecov/codecov-action@v1
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# file: ./build/coverage.xml
# flags: unittests
38 changes: 38 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,44 @@ add_subdirectory(bindings)
option(WITH_MAN "Generate on-line reference manuals (man pages)" OFF)
add_subdirectory(man)

# Testing
option(TESTS "Enabling unit testing" OFF)
option(TESTS_DEBUG "Enable test debugging" OFF)
option(TESTS_COVERAGE "Enable coverage tracing when testing" OFF)
if(TESTS)
include(CTest)
enable_testing()
add_subdirectory(tests)
if(TESTS_COVERAGE)
if(NOT WITH_GCOV)
message(FATAL_ERROR "Coverage report generation only supported with WITH_GCOV also enabled")
endif()

if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Linux")
message(FATAL_ERROR "Coverage only supported on Linux")
endif()
# Verify that lcov is installed
find_program(LCOV_PATH lcov)
if(NOT LCOV_PATH)
message(FATAL_ERROR "lcov not found! Aborting...")
endif()
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -Wall -fprofile-arcs -ftest-coverage")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -Wall -W -fprofile-arcs -ftest-coverage")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage")
target_compile_options(iio PRIVATE --coverage)
target_link_libraries(iio PRIVATE --coverage)

# Add custom target to run tests with coverage
add_custom_target(
coverage
COMMAND ${CMAKE_CTEST_COMMAND} --progress && lcov -c -d ${CMAKE_CURRENT_SOURCE_DIR} -o main_coverage.info && genhtml main_coverage.info -o coverage
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})

message(STATUS "Coverage flags enabled")
endif()

endif()

include(cmake/Install.cmake)

# Add uninstall target
Expand Down
3 changes: 3 additions & 0 deletions README_BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ Cmake Options | Default | Target | Description
`WITH_GCOV` | OFF | Linux | Build with gcov profiling flags |
`OSX_FRAMEWORK` | ON | Mac | OS X frameworks provide the interfaces you need to write software for Mac. |
`OSX_PACKAGE` | ON | Mac | Create a OSX package for installation on local and other machines |
`TESTS` | OFF | All | Build tests and enable tests targets |
`TESTS_DEBUG` | OFF | All | Build tests with debug outputs |
`TESTS_COVERAGE` | OFF | Linux | Enable debug flags and coverage report generation |

Which backends the library supports is dependent on the build system, but can be overridden.
(If cmake finds libusb, it will use it, unless turned off manually)
Expand Down
3 changes: 3 additions & 0 deletions bindings/python/requirements_test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest
scipy
numpy
152 changes: 152 additions & 0 deletions bindings/python/tests/tests_ad9361.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import pytest

import iio
import numpy as np
from scipy import signal

import os


def gen_data(fs, fc):
N = 2**12
ts = 1 / float(fs)
t = np.arange(0, N * ts, ts)
i = np.cos(2 * np.pi * t * fc) * 2**14
q = np.sin(2 * np.pi * t * fc) * 2**14
iq = i + 1j * q
return iq


def estimate_freq(x, fs):
f, Pxx_den = signal.periodogram(x, fs)
idx = np.argmax(Pxx_den)
return f[idx]


## Streaming not supported yet
# def stream_based_tx(buf_tx, iq):
# # Stream version (Does not support cyclic?)
# tx_stream = iio.Stream(buf_tx, 1024)
# block_tx = next(tx_stream)
# block_tx.write(iq)
# block_tx.enqueue(None, True)
# buf_tx.enabled = True


def block_based_tx_chans(block_tx, buf_tx, mask_tx, ib, qb):
# Block version channel based
mask_tx.channels[0].write(block_tx, ib)
mask_tx.channels[1].write(block_tx, qb)
block_tx.enqueue(None, True)
buf_tx.enabled = True


def block_based_tx_single(block_tx, buf_tx, ib, qb):
iqb = np.stack((ib, qb), axis=-1)
iqb = iqb.flatten()
iqb = bytearray(iqb)

# Block version single write
block_tx.write(iqb)
block_tx.enqueue(None, True)
buf_tx.enabled = True


@pytest.mark.parametrize("tx_buffer_modes", ["dds", "chans", "single"])
@pytest.mark.parametrize("rx_buffer_modes", ["chans", "single"])
def test_ad9361_buffers(tx_buffer_modes, rx_buffer_modes):
fs = 4e6
lo = 1e9
fc = 5e5

uri = os.getenv("URI", "ip:analog.local")

ctx = iio.Context(uri)
dev = ctx.find_device("ad9361-phy")
dev_rx = ctx.find_device("cf-ad9361-lpc")
dev_tx = ctx.find_device("cf-ad9361-dds-core-lpc")

chan = dev.find_channel("voltage0")
chan.attrs["sampling_frequency"].value = str(int(fs))
chan.attrs["rf_bandwidth"].value = str(int(fs))

achan = dev.find_channel("altvoltage0", True)
achan.attrs["frequency"].value = str(int(lo))
achan = dev.find_channel("altvoltage1", True)
achan.attrs["frequency"].value = str(int(lo))

dev.debug_attrs["loopback"].value = "1"

if tx_buffer_modes == "dds":
# DDS
for N in [1]:
for IQ in ["I", "Q"]:
chan = f"TX1_{IQ}_F{N}"
dds = dev_tx.find_channel(chan, True)
if not dds:
raise Exception(f"Could not find channel {chan}")
dds.attrs["frequency"].value = str(int(fc))
dds.attrs["scale"].value = "0.2"
if IQ == "I":
dds.attrs["phase"].value = "90000"
else:
dds.attrs["phase"].value = "0.0"

## Buffer stuff

# RX Side
chan1 = dev_rx.find_channel("voltage0")
chan2 = dev_rx.find_channel("voltage1")
mask = iio.ChannelsMask(dev_rx)
mask.channels = [chan1, chan2]

buf = iio.Buffer(dev_rx, mask)
stream = iio.Stream(buf, 1024)

if tx_buffer_modes != "dds":
# TX Side
chan1_tx = dev_tx.find_channel("voltage0", True)
chan2_tx = dev_tx.find_channel("voltage1", True)
mask_tx = iio.ChannelsMask(dev_tx)
mask_tx.channels = [chan1_tx, chan2_tx]

# Create a sinewave waveform
fc = 15e5
iq = gen_data(fs, fc)
# Convert iq to interleaved int16 byte array
ib = np.array(iq.real, dtype=np.int16)
qb = np.array(iq.imag, dtype=np.int16)

# Send data to iio
buf_tx = iio.Buffer(dev_tx, mask_tx)
block_tx = iio.Block(buf_tx, len(ib))

if tx_buffer_modes == "chans":
block_based_tx_chans(block_tx, buf_tx, mask_tx, ib, qb)

if tx_buffer_modes == "single":
block_based_tx_single(block_tx, buf_tx, ib, qb)

# Clear buffer queue
for r in range(10):
block = next(stream)

for r in range(20):
block = next(stream)

# Single buffer read
if rx_buffer_modes == "single":
x = np.frombuffer(block.read(), dtype=np.int16)
x = x[0::2] + 1j * x[1::2]
else:
# Buffer read by channel
re = mask.channels[0].read(block)
re = np.frombuffer(re, dtype=np.int16)
im = mask.channels[1].read(block)
im = np.frombuffer(im, dtype=np.int16)
x = re + 1j * im

freq = estimate_freq(x, fs)
print(f"Estimated freq: {freq/1e6} MHz | Expected freq: {fc/1e6} MHz")

assert np.abs(freq - fc) < 1e3
18 changes: 3 additions & 15 deletions block.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ struct iio_block {
size_t size;
void *data;

struct iio_task_token *token, *old_token;
struct iio_task_token *token;
size_t bytes_used;
bool cyclic;
};

struct iio_block *
Expand Down Expand Up @@ -83,9 +82,6 @@ void iio_block_destroy(struct iio_block *block)
struct iio_buffer *buf = block->buffer;
const struct iio_backend_ops *ops = buf->dev->ctx->ops;

/* Stop the cyclic task */
block->cyclic = false;

if (block->token) {
iio_task_cancel(block->token);
iio_task_sync(block->token, 0);
Expand Down Expand Up @@ -132,20 +128,12 @@ int iio_block_io(struct iio_block *block)
if (!iio_device_is_tx(block->buffer->dev))
return iio_block_read(block);

if (block->old_token)
iio_task_sync(block->old_token, 0);

if (block->cyclic) {
block->old_token = block->token;
block->token = iio_task_enqueue(block->buffer->worker, block);
}

return iio_block_write(block);
}

int iio_block_enqueue(struct iio_block *block, size_t bytes_used, bool cyclic)
{
const struct iio_buffer *buffer = block->buffer;
struct iio_buffer *buffer = block->buffer;
const struct iio_device *dev = buffer->dev;
const struct iio_backend_ops *ops = dev->ctx->ops;

Expand All @@ -164,7 +152,7 @@ int iio_block_enqueue(struct iio_block *block, size_t bytes_used, bool cyclic)
}

block->bytes_used = bytes_used;
block->cyclic = cyclic;
buffer->cyclic = cyclic;
block->token = iio_task_enqueue(buffer->worker, block);

return iio_err(block->token);
Expand Down
4 changes: 3 additions & 1 deletion buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,16 @@ static int iio_buffer_set_enabled(const struct iio_buffer *buf, bool enabled)
{
const struct iio_backend_ops *ops = buf->dev->ctx->ops;
size_t sample_size, nb_samples = 0;
bool cyclic = false;

if (buf->block_size) {
sample_size = iio_device_get_sample_size(buf->dev, buf->mask);
nb_samples = buf->block_size / sample_size;
cyclic = buf->cyclic;
}

if (ops->enable_buffer)
return ops->enable_buffer(buf->pdata, nb_samples, enabled);
return ops->enable_buffer(buf->pdata, nb_samples, enabled, cyclic);

return -ENOSYS;
}
Expand Down
3 changes: 3 additions & 0 deletions iio-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,10 @@ struct iio_buffer {

struct iio_task *worker;

/* These two fields are set by the last block created. They are only
* used when communicating with v0.x IIOD. */
size_t block_size;
bool cyclic;

struct iio_attr_list attrlist;

Expand Down
4 changes: 2 additions & 2 deletions iiod-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -1490,7 +1490,7 @@ void iiod_client_free_buffer(struct iiod_client_buffer_pdata *pdata)
}

int iiod_client_enable_buffer(struct iiod_client_buffer_pdata *pdata,
size_t nb_samples, bool enable)
size_t nb_samples, bool enable, bool cyclic)
{
struct iiod_client *client = pdata->client;
struct iiod_client_io *client_io;
Expand All @@ -1511,7 +1511,7 @@ int iiod_client_enable_buffer(struct iiod_client_buffer_pdata *pdata,
if (enable) {
client_io = iiod_client_open_with_mask(client, pdata->dev,
pdata->mask,
nb_samples, false);
nb_samples, cyclic);
err = iio_err(client_io);
pdata->io = err ? NULL : client_io;
} else {
Expand Down
2 changes: 1 addition & 1 deletion iiod/ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,8 @@ static int create_buf_and_blocks(struct DevEntry *entry, size_t samples_count,
for (; i; i--)
iio_block_destroy(entry->blocks[i - 1]);
iio_buffer_destroy(entry->buf);
entry->buf = NULL;
err_free_blocks_array:
entry->buf = NULL;
free(entry->blocks);
entry->blocks = NULL;
return err;
Expand Down
2 changes: 1 addition & 1 deletion include/iio/iio-backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ struct iio_backend_ops {
struct iio_channels_mask *mask);
void (*free_buffer)(struct iio_buffer_pdata *pdata);
int (*enable_buffer)(struct iio_buffer_pdata *pdata,
size_t nb_samples, bool enable);
size_t nb_samples, bool enable, bool cyclic);
void (*cancel_buffer)(struct iio_buffer_pdata *pdata);

ssize_t (*readbuf)(struct iio_buffer_pdata *pdata,
Expand Down
2 changes: 1 addition & 1 deletion include/iio/iiod-client.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ iiod_client_create_buffer(struct iiod_client *client,
struct iio_channels_mask *mask);
__api void iiod_client_free_buffer(struct iiod_client_buffer_pdata *pdata);
__api int iiod_client_enable_buffer(struct iiod_client_buffer_pdata *pdata,
size_t nb_samples, bool enable);
size_t nb_samples, bool enable, bool cyclic);

__api struct iio_block_pdata *
iiod_client_create_block(struct iiod_client_buffer_pdata *pdata,
Expand Down
2 changes: 1 addition & 1 deletion local.c
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ static int local_do_enable_buffer(struct iio_buffer_pdata *pdata, bool enable)
}

static int local_enable_buffer(struct iio_buffer_pdata *pdata,
size_t nb_samples, bool enable)
size_t nb_samples, bool enable, bool cyclic)
{
int ret;

Expand Down
4 changes: 2 additions & 2 deletions network.c
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,9 @@ void network_free_buffer(struct iio_buffer_pdata *pdata)
}

int network_enable_buffer(struct iio_buffer_pdata *pdata,
size_t block_size, bool enable)
size_t block_size, bool enable, bool cyclic)
{
return iiod_client_enable_buffer(pdata->pdata, block_size, enable);
return iiod_client_enable_buffer(pdata->pdata, block_size, enable, cyclic);
}

struct iio_block_pdata * network_create_block(struct iio_buffer_pdata *pdata,
Expand Down
Loading
Loading