Skip to content

Commit

Permalink
. WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
rpj committed Aug 17, 2023
1 parent c6261d8 commit 3edbe97
Show file tree
Hide file tree
Showing 15 changed files with 449 additions and 119 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ jobs:
run: |
sudo apt -y install redis && \
sudo systemctl start redis
- name: Build & run tests
- name: Build tests
run: cd test && make
- name: Run tests
run: cd test && make run
2 changes: 1 addition & 1 deletion Redis.h
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ class Redis
* @param returnString
* @returns true if returnString is the nil return value, false otherwise
*/
bool isNilReturn(String returnString) { return returnString == "(nil)"; }
static bool isNilReturn(String returnString) { return returnString == "(nil)"; }

/**
* Enters subscription mode and subscribes to all channels/patterns setup via `subscribe()`/`psubscribe()`.
Expand Down
8 changes: 8 additions & 0 deletions RedisInternal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,16 @@ String RedisBulkString::RESP()

void RedisArray::init(Client &client)
{
// Null array https://redis.io/docs/reference/protocol-spec/#null-arrays
if (data.toInt() == -1)
{
return;
}

for (int i = 0; i < data.toInt(); i++)
{
add(RedisObject::parseType(client));
}
}

RedisArray::operator std::vector<std::shared_ptr<RedisObject>>() const
Expand Down
5 changes: 5 additions & 0 deletions RedisInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ class RedisArray : public RedisObject

operator std::vector<std::shared_ptr<RedisObject>>() const;

/** Returns false if this is a "Null Array" (https://redis.io/docs/reference/protocol-spec/#null-arrays),
* true otherwise (including if the array is empty!)
*/
bool isNilReturn() const { return data.toInt() == -1; }

virtual void init(Client &client) override;

virtual String RESP() override;
Expand Down
17 changes: 17 additions & 0 deletions test/ArduinoRedisTestBase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#define ArduinoRedisTestCommonSetupAndLoop \
void setup() \
{ \
const char *include = std::getenv("ARDUINO_REDIS_TEST_INCLUDE"); \
\
if (!include) \
{ \
include = "*"; \
} \
\
TestRunner::include(include); \
} \
\
void loop() \
{ \
TestRunner::run(); \
}
89 changes: 89 additions & 0 deletions test/IntegrationTestBase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include <Redis.h>
#include <RedisInternal.h>

#include <AUnitVerbose.h>

#include <memory>

#include "./TestRawClient.h"

const String gKeyPrefix = String("__arduino_redis__test");

#define prefixKey(k) (String(gKeyPrefix + "." + k))
#define prefixKeyCStr(k) (String(gKeyPrefix + "." + k).c_str())

#define defineKey(KEY) \
auto __ks = prefixKey(KEY); \
auto k = __ks.c_str();

std::pair<std::shared_ptr<TestRawClient>, std::shared_ptr<Redis>> NewConnection()
{
std::pair<std::shared_ptr<TestRawClient>, std::shared_ptr<Redis>> ret_val = std::make_pair(nullptr, nullptr);

const char *r_host = std::getenv("ARDUINO_REDIS_TEST_HOST");
if (!r_host)
{
r_host = "localhost";
}

const char *r_port = std::getenv("ARDUINO_REDIS_TEST_PORT");
if (!r_port)
{
r_port = "6379";
}

auto r_auth = std::getenv("ARDUINO_REDIS_TEST_AUTH");

auto client = std::make_shared<TestRawClient>();
if (client->connect(r_host, std::atoi(r_port)) == 0)
{
return ret_val;
}

Client &c_ref = *client.get();
auto r = std::make_shared<Redis>(c_ref);
if (r_auth)
{
auto auth_ret = r->authenticate(r_auth);
if (auth_ret == RedisReturnValue::RedisAuthFailure)
{
return ret_val;
}
}

return std::make_pair(client, r);
}

// Any testF(IntegrationTests, ...) defined will automatically have scope access to `r`, the redis client
class IntegrationTests : public aunit::TestOnce
{
protected:
void setup() override
{
aunit::TestOnce::setup();
auto conn_ret = NewConnection();
assertNotEqual(conn_ret.first.get(), nullptr);
assertNotEqual(conn_ret.second.get(), nullptr);
client = std::move(conn_ret.first);
r = std::move(conn_ret.second);
}

void teardown() override
{
auto keys = RedisCommand("KEYS", ArgList{String(gKeyPrefix + "*").c_str()}).issue(*client);

if (keys->type() == RedisObject::Type::Array)
{
std::vector<String> as_strings = *dynamic_cast<RedisArray *>(keys.get());
for (const auto &key : as_strings)
{
r->del(key.c_str());
}
}

aunit::TestOnce::teardown();
}

std::shared_ptr<TestRawClient> client;
std::shared_ptr<Redis> r;
};
15 changes: 10 additions & 5 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ TESTS = unit/unit-tests.out integration/integration-tests.out

test : $(TESTS)

unit/unit-tests.out:
unit/unit-tests.out: unit/unit-tests.ino ../Redis.h ../Redis.cpp ../RedisInternal.h ../RedisInternal.cpp
cd unit && make

integration/integration-tests.out:
integration/integration-tests.out: integration/integration-tests.ino ../Redis.h ../Redis.cpp ../RedisInternal.h ../RedisInternal.cpp
cd integration && make

run: clean test
pubsub: pubsub/subscriber/subscriber-tests.out pubsub/publisher/publisher-tests.out
cd pubsub && make

run: test pubsub
./unit/unit-tests.out
./integration/integration-tests.out
cd pubsub && make run

clean:
rm -f unit/unit-tests.out
rm -f integration/integration-tests.out
rm -f ../*.o
cd unit && make clean
cd integration && make clean
158 changes: 74 additions & 84 deletions test/integration/integration-tests.ino
Original file line number Diff line number Diff line change
Expand Up @@ -7,89 +7,13 @@
#include <AUnitVerbose.h>

#include <map>
#include <memory>

#include "../TestRawClient.h"
#include "../ArduinoRedisTestBase.h"
#include "../IntegrationTestBase.h"

using namespace aunit;

const String gKeyPrefix = String("__arduino_redis__test");

void setup()
{
const char *include = std::getenv("ARDUINO_REDIS_TEST_INCLUDE");

if (!include)
{
include = "*";
}

TestRunner::include(include);
}

void loop()
{
TestRunner::run();
}

class IntegrationTests : public TestOnce
{
protected:
void setup() override
{
TestOnce::setup();

const char *r_host = std::getenv("ARDUINO_REDIS_TEST_HOST");
if (!r_host)
{
r_host = "localhost";
}

const char *r_port = std::getenv("ARDUINO_REDIS_TEST_PORT");
if (!r_port)
{
r_port = "6379";
}

auto r_auth = std::getenv("ARDUINO_REDIS_TEST_AUTH");

assertNotEqual(client.connect(r_host, std::atoi(r_port)), 0);
r = std::make_shared<Redis>(client);

if (r_auth)
{
auto auth_ret = r->authenticate(r_auth);
assertNotEqual(auth_ret, RedisReturnValue::RedisAuthFailure);
}
}

void teardown() override
{
auto keys = RedisCommand("KEYS", ArgList{String(gKeyPrefix + "*").c_str()}).issue(client);

if (keys->type() == RedisObject::Type::Array)
{
std::vector<String> as_strings = *dynamic_cast<RedisArray*>(keys.get());
for (const auto& key : as_strings) {
r->del(key.c_str());
}
}

TestOnce::teardown();
}

TestRawClient client;
std::shared_ptr<Redis> r;
};

#define prefixKey(k) (String(gKeyPrefix + "." + k))
#define prefixKeyCStr(k) (String(gKeyPrefix + "." + k).c_str())

#define defineKey(KEY) \
auto __ks = prefixKey(KEY); \
auto k = __ks.c_str();

// Any testF(IntegrationTests, ...) defined will automatically have scope access to `r`, the redis client
ArduinoRedisTestCommonSetupAndLoop;

testF(IntegrationTests, set)
{
Expand Down Expand Up @@ -119,7 +43,7 @@ testF(IntegrationTests, expire_at)

assertEqual(r->set(k, "E"), true);
assertEqual(r->expire_at(k, 0), true);
assertEqual(r->isNilReturn(r->get(k)), true);
assertEqual(Redis::isNilReturn(r->get(k)), true);
}

testF(IntegrationTests, pexpire)
Expand All @@ -137,7 +61,7 @@ testF(IntegrationTests, pexpire_at)

assertEqual(r->set(k, "PEA"), true);
assertEqual(r->pexpire_at(k, 0), true);
assertEqual(r->isNilReturn(r->get(k)), true);
assertEqual(Redis::isNilReturn(r->get(k)), true);
}

testF(IntegrationTests, ttl)
Expand Down Expand Up @@ -237,7 +161,7 @@ testF(IntegrationTests, hdel)

assertEqual(r->hset(k, "delete_me", k), true);
assertEqual(r->hdel(k, "delete_me"), true);
assertEqual(r->isNilReturn(r->hget(k, "delete_me")), true);
assertEqual(Redis::isNilReturn(r->hget(k, "delete_me")), true);
}

testF(IntegrationTests, append)
Expand Down Expand Up @@ -327,14 +251,14 @@ testF(IntegrationTests, hgetnil)
{
auto nothing = r->hget(prefixKeyCStr("hgetnil"), "doesNotExist");
assertEqual(nothing, String("(nil)"));
assertEqual(r->isNilReturn(nothing), true);
assertEqual(Redis::isNilReturn(nothing), true);
}

testF(IntegrationTests, lindexnil)
{
auto nothing = r->lindex(prefixKeyCStr("lindexnil"), 0);
assertEqual(nothing, String("(nil)"));
assertEqual(r->isNilReturn(nothing), true);
assertEqual(Redis::isNilReturn(nothing), true);
}

testF(IntegrationTests, op_vec_string_issue67)
Expand All @@ -353,6 +277,72 @@ testF(IntegrationTests, op_vec_string_issue67)
assertEqual(list[2], "3");
}

class SubscriberTests : public TestOnce
{
protected:
void setup() override
{
TestOnce::setup();

const char *r_host = std::getenv("ARDUINO_REDIS_TEST_HOST");
if (!r_host)
{
r_host = "localhost";
}

const char *r_port = std::getenv("ARDUINO_REDIS_TEST_PORT");
if (!r_port)
{
r_port = "6379";
}

auto r_auth = std::getenv("ARDUINO_REDIS_TEST_AUTH");

assertNotEqual(client1.connect(r_host, std::atoi(r_port)), 0);
assertNotEqual(client2.connect(r_host, std::atoi(r_port)), 0);
redis1 = std::make_shared<Redis>(client1);
redis2 = std::make_shared<Redis>(client1);

if (r_auth)
{
auto auth_ret = redis1->authenticate(r_auth);
assertNotEqual(auth_ret, RedisReturnValue::RedisAuthFailure);
auth_ret = redis2->authenticate(r_auth);
assertNotEqual(auth_ret, RedisReturnValue::RedisAuthFailure);
}
printf("BALLS %p %p\n", redis1.get(), redis2.get());
}

TestRawClient client1, client2;
std::shared_ptr<Redis> redis1, redis2;
};

testF(SubscriberTests, subscribe_simple)
{
/*
auto sub_key = gKeyPrefix + ":subscribe_simple";
printf("CHAN %s\n", sub_key.c_str());
assertEqual(redis1->subscribe(sub_key.c_str()), true);
redis2->publish(sub_key.c_str(), "test1!");
auto sub_res = redis1->startSubscribingNonBlocking([](Redis *rconn, String chan, String message) {
printf("MESSAGE!!!! chan=%s '%s'\n", chan.c_str(), message.c_str());
},
[]() {
printf("LOOP!\n");
},
[](Redis *redisInst, RedisMessageError err) {
printf("ERROR!\n");
});
printf("sub_res %d\n", sub_res);
redis2->publish(sub_key.c_str(), "test2!");
delay(1000);
printf("DONE!\n");
*/
}

/* TODO: re-factor this to something that can be automated!!
#define SUBSCRIBE_TESTS 0
Expand Down
Loading

0 comments on commit 3edbe97

Please sign in to comment.