You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1787 lines
53 KiB
1787 lines
53 KiB
/* |
|
* Copyright (C) 2013-2014 The Android Open Source Project |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
|
|
#include <ctype.h> |
|
#include <dirent.h> |
|
#include <signal.h> |
|
#include <stdint.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/cdefs.h> |
|
#include <sys/stat.h> |
|
#include <sys/types.h> |
|
#include <sys/wait.h> |
|
#include <unistd.h> |
|
|
|
#include <memory> |
|
#include <string> |
|
|
|
#include <android-base/file.h> |
|
#include <android-base/stringprintf.h> |
|
#include <gtest/gtest.h> |
|
#include <log/event_tag_map.h> |
|
#include <log/log.h> |
|
#include <log/log_event_list.h> |
|
|
|
#ifndef logcat_popen |
|
#define logcat_define(context) |
|
#define logcat_popen(context, command) popen((command), "r") |
|
#define logcat_pclose(context, fp) pclose(fp) |
|
#define logcat_system(command) system(command) |
|
#endif |
|
#ifndef logcat_executable |
|
#define USING_LOGCAT_EXECUTABLE_DEFAULT |
|
#define logcat_executable "logcat" |
|
#endif |
|
|
|
#define BIG_BUFFER (5 * 1024) |
|
|
|
// rest(), let the logs settle. |
|
// |
|
// logd is in a background cgroup and under extreme load can take up to |
|
// 3 seconds to land a log entry. Under moderate load we can do with 200ms. |
|
static void rest() { |
|
static const useconds_t restPeriod = 200000; |
|
|
|
usleep(restPeriod); |
|
} |
|
|
|
// enhanced version of LOG_FAILURE_RETRY to add support for EAGAIN and |
|
// non-syscall libs. Since we are only using this in the emergency of |
|
// a signal to stuff a terminating code into the logs, we will spin rather |
|
// than try a usleep. |
|
#define LOG_FAILURE_RETRY(exp) \ |
|
({ \ |
|
typeof(exp) _rc; \ |
|
do { \ |
|
_rc = (exp); \ |
|
} while (((_rc == -1) && ((errno == EINTR) || (errno == EAGAIN))) || \ |
|
(_rc == -EINTR) || (_rc == -EAGAIN)); \ |
|
_rc; \ |
|
}) |
|
|
|
static const char begin[] = "--------- beginning of "; |
|
|
|
TEST(logcat, buckets) { |
|
FILE* fp; |
|
logcat_define(ctx); |
|
|
|
#undef LOG_TAG |
|
#define LOG_TAG "inject.buckets" |
|
// inject messages into radio, system, main and events buffers to |
|
// ensure that we see all the begin[] bucket messages. |
|
RLOGE(logcat_executable); |
|
SLOGE(logcat_executable); |
|
ALOGE(logcat_executable); |
|
__android_log_bswrite(0, logcat_executable ".inject.buckets"); |
|
rest(); |
|
|
|
ASSERT_TRUE(NULL != |
|
(fp = logcat_popen( |
|
ctx, logcat_executable |
|
" -b radio -b events -b system -b main -d 2>/dev/null"))); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
int ids = 0; |
|
int count = 0; |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
if (!strncmp(begin, buffer, sizeof(begin) - 1)) { |
|
while (char* cp = strrchr(buffer, '\n')) { |
|
*cp = '\0'; |
|
} |
|
log_id_t id = android_name_to_log_id(buffer + sizeof(begin) - 1); |
|
ids |= 1 << id; |
|
++count; |
|
} |
|
} |
|
|
|
logcat_pclose(ctx, fp); |
|
|
|
EXPECT_EQ(ids, 15); |
|
|
|
EXPECT_EQ(count, 4); |
|
} |
|
|
|
TEST(logcat, event_tag_filter) { |
|
FILE* fp; |
|
logcat_define(ctx); |
|
|
|
#undef LOG_TAG |
|
#define LOG_TAG "inject.filter" |
|
// inject messages into radio, system and main buffers |
|
// with our unique log tag to test logcat filter. |
|
RLOGE(logcat_executable); |
|
SLOGE(logcat_executable); |
|
ALOGE(logcat_executable); |
|
rest(); |
|
|
|
std::string command = android::base::StringPrintf( |
|
logcat_executable |
|
" -b radio -b system -b main --pid=%d -d -s inject.filter 2>/dev/null", |
|
getpid()); |
|
ASSERT_TRUE(NULL != (fp = logcat_popen(ctx, command.c_str()))); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
int count = 0; |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
if (strncmp(begin, buffer, sizeof(begin) - 1)) ++count; |
|
} |
|
|
|
logcat_pclose(ctx, fp); |
|
|
|
// logcat, liblogcat and logcatd test instances result in the progression |
|
// of 3, 6 and 9 for our counts as each round is performed. |
|
EXPECT_GE(count, 3); |
|
EXPECT_LE(count, 9); |
|
EXPECT_EQ(count % 3, 0); |
|
} |
|
|
|
// If there is not enough background noise in the logs, then spam the logs to |
|
// permit tail checking so that the tests can progress. |
|
static size_t inject(ssize_t count) { |
|
if (count <= 0) return 0; |
|
|
|
static const size_t retry = 4; |
|
size_t errors = retry; |
|
size_t num = 0; |
|
for (;;) { |
|
log_time ts(CLOCK_MONOTONIC); |
|
if (__android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)) >= 0) { |
|
if (++num >= (size_t)count) { |
|
// let data settle end-to-end |
|
sleep(3); |
|
return num; |
|
} |
|
errors = retry; |
|
usleep(100); // ~32 per timer tick, we are a spammer regardless |
|
} else if (--errors <= 0) { |
|
return num; |
|
} |
|
} |
|
// NOTREACH |
|
return num; |
|
} |
|
|
|
TEST(logcat, year) { |
|
if (android_log_clockid() == CLOCK_MONOTONIC) { |
|
fprintf(stderr, "Skipping test, logd is monotonic time\n"); |
|
return; |
|
} |
|
|
|
int count; |
|
int tries = 3; // in case run too soon after system start or buffer clear |
|
|
|
do { |
|
FILE* fp; |
|
logcat_define(ctx); |
|
|
|
char needle[32]; |
|
time_t now; |
|
time(&now); |
|
struct tm* ptm; |
|
#if !defined(_WIN32) |
|
struct tm tmBuf; |
|
ptm = localtime_r(&now, &tmBuf); |
|
#else |
|
ptm = localtime(&&now); |
|
#endif |
|
strftime(needle, sizeof(needle), "[ %Y-", ptm); |
|
|
|
ASSERT_TRUE(NULL != (fp = logcat_popen( |
|
ctx, logcat_executable |
|
" -v long -v year -b all -t 3 2>/dev/null"))); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
count = 0; |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
if (!strncmp(buffer, needle, strlen(needle))) { |
|
++count; |
|
} |
|
} |
|
logcat_pclose(ctx, fp); |
|
|
|
} while ((count < 3) && --tries && inject(3 - count)); |
|
|
|
ASSERT_EQ(3, count); |
|
} |
|
|
|
// Return a pointer to each null terminated -v long time field. |
|
static char* fgetLongTime(char* buffer, size_t buflen, FILE* fp) { |
|
while (fgets(buffer, buflen, fp)) { |
|
char* cp = buffer; |
|
if (*cp != '[') { |
|
continue; |
|
} |
|
while (*++cp == ' ') { |
|
; |
|
} |
|
char* ep = cp; |
|
while (isdigit(*ep)) { |
|
++ep; |
|
} |
|
if ((*ep != '-') && (*ep != '.')) { |
|
continue; |
|
} |
|
// Find PID field. Look for ': ' or ':[0-9][0-9][0-9]' |
|
while (((ep = strchr(ep, ':'))) && (*++ep != ' ')) { |
|
if (isdigit(ep[0]) && isdigit(ep[1]) && isdigit(ep[2])) break; |
|
} |
|
if (!ep) { |
|
continue; |
|
} |
|
static const size_t pid_field_width = 7; |
|
ep -= pid_field_width; |
|
*ep = '\0'; |
|
return cp; |
|
} |
|
return NULL; |
|
} |
|
|
|
TEST(logcat, tz) { |
|
if (android_log_clockid() == CLOCK_MONOTONIC) { |
|
fprintf(stderr, "Skipping test, logd is monotonic time\n"); |
|
return; |
|
} |
|
|
|
int tries = 4; // in case run too soon after system start or buffer clear |
|
int count; |
|
|
|
do { |
|
FILE* fp; |
|
logcat_define(ctx); |
|
|
|
ASSERT_TRUE(NULL != |
|
(fp = logcat_popen(ctx, logcat_executable |
|
" -v long -v America/Los_Angeles " |
|
"-b all -t 3 2>/dev/null"))); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
count = 0; |
|
|
|
while (fgetLongTime(buffer, sizeof(buffer), fp)) { |
|
if (strstr(buffer, " -0700") || strstr(buffer, " -0800")) { |
|
++count; |
|
} else { |
|
fprintf(stderr, "ts=\"%s\"\n", buffer + 2); |
|
} |
|
} |
|
|
|
logcat_pclose(ctx, fp); |
|
|
|
} while ((count < 3) && --tries && inject(3 - count)); |
|
|
|
ASSERT_EQ(3, count); |
|
} |
|
|
|
TEST(logcat, ntz) { |
|
FILE* fp; |
|
logcat_define(ctx); |
|
|
|
ASSERT_TRUE(NULL != (fp = logcat_popen(ctx, logcat_executable |
|
" -v long -v America/Los_Angeles -v " |
|
"zone -b all -t 3 2>/dev/null"))); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
int count = 0; |
|
|
|
while (fgetLongTime(buffer, sizeof(buffer), fp)) { |
|
if (strstr(buffer, " -0700") || strstr(buffer, " -0800")) { |
|
++count; |
|
} |
|
} |
|
|
|
logcat_pclose(ctx, fp); |
|
|
|
ASSERT_EQ(0, count); |
|
} |
|
|
|
static void do_tail(int num) { |
|
int tries = 4; // in case run too soon after system start or buffer clear |
|
int count; |
|
|
|
if (num > 10) ++tries; |
|
if (num > 100) ++tries; |
|
do { |
|
char buffer[BIG_BUFFER]; |
|
|
|
snprintf(buffer, sizeof(buffer), |
|
"ANDROID_PRINTF_LOG=long logcat -b all -t %d 2>/dev/null", num); |
|
|
|
FILE* fp; |
|
logcat_define(ctx); |
|
ASSERT_TRUE(NULL != (fp = logcat_popen(ctx, buffer))); |
|
|
|
count = 0; |
|
|
|
while (fgetLongTime(buffer, sizeof(buffer), fp)) { |
|
++count; |
|
} |
|
|
|
logcat_pclose(ctx, fp); |
|
|
|
} while ((count < num) && --tries && inject(num - count)); |
|
|
|
ASSERT_EQ(num, count); |
|
} |
|
|
|
TEST(logcat, tail_3) { |
|
do_tail(3); |
|
} |
|
|
|
TEST(logcat, tail_10) { |
|
do_tail(10); |
|
} |
|
|
|
TEST(logcat, tail_100) { |
|
do_tail(100); |
|
} |
|
|
|
TEST(logcat, tail_1000) { |
|
do_tail(1000); |
|
} |
|
|
|
static void do_tail_time(const char* cmd) { |
|
FILE* fp; |
|
int count; |
|
char buffer[BIG_BUFFER]; |
|
char* last_timestamp = NULL; |
|
// Hard to predict 100% if first (overlap) or second line will match. |
|
// -v nsec will in a substantial majority be the second line. |
|
char* first_timestamp = NULL; |
|
char* second_timestamp = NULL; |
|
char* input; |
|
|
|
int tries = 4; // in case run too soon after system start or buffer clear |
|
|
|
do { |
|
snprintf(buffer, sizeof(buffer), "%s -t 10 2>&1", cmd); |
|
logcat_define(ctx); |
|
ASSERT_TRUE(NULL != (fp = logcat_popen(ctx, buffer))); |
|
count = 0; |
|
|
|
while ((input = fgetLongTime(buffer, sizeof(buffer), fp))) { |
|
++count; |
|
if (!first_timestamp) { |
|
first_timestamp = strdup(input); |
|
} else if (!second_timestamp) { |
|
second_timestamp = strdup(input); |
|
} |
|
free(last_timestamp); |
|
last_timestamp = strdup(input); |
|
} |
|
logcat_pclose(ctx, fp); |
|
|
|
} while ((count < 10) && --tries && inject(10 - count)); |
|
|
|
EXPECT_EQ(count, 10); // We want _some_ history, too small, falses below |
|
EXPECT_TRUE(last_timestamp != NULL); |
|
EXPECT_TRUE(first_timestamp != NULL); |
|
EXPECT_TRUE(second_timestamp != NULL); |
|
|
|
snprintf(buffer, sizeof(buffer), "%s -t '%s' 2>&1", cmd, first_timestamp); |
|
logcat_define(ctx); |
|
ASSERT_TRUE(NULL != (fp = logcat_popen(ctx, buffer))); |
|
|
|
int second_count = 0; |
|
int last_timestamp_count = -1; |
|
|
|
--count; // One less unless we match the first_timestamp |
|
bool found = false; |
|
while ((input = fgetLongTime(buffer, sizeof(buffer), fp))) { |
|
++second_count; |
|
// We want to highlight if we skip to the next entry. |
|
// WAI, if the time in logd is *exactly* |
|
// XX-XX XX:XX:XX.XXXXXX000 (usec) or XX-XX XX:XX:XX.XXX000000 |
|
// this can happen, but it should not happen with nsec. |
|
// We can make this WAI behavior happen 1000 times less |
|
// frequently if the caller does not use the -v usec flag, |
|
// but always the second (always skip) if they use the |
|
// (undocumented) -v nsec flag. |
|
if (first_timestamp) { |
|
found = !strcmp(input, first_timestamp); |
|
if (found) { |
|
++count; |
|
GTEST_LOG_(INFO) |
|
<< "input = first(" << first_timestamp << ")\n"; |
|
} |
|
free(first_timestamp); |
|
first_timestamp = NULL; |
|
} |
|
if (second_timestamp) { |
|
found = found || !strcmp(input, second_timestamp); |
|
if (!found) { |
|
GTEST_LOG_(INFO) << "input(" << input << ") != second(" |
|
<< second_timestamp << ")\n"; |
|
} |
|
free(second_timestamp); |
|
second_timestamp = NULL; |
|
} |
|
if (!strcmp(input, last_timestamp)) { |
|
last_timestamp_count = second_count; |
|
} |
|
} |
|
logcat_pclose(ctx, fp); |
|
|
|
EXPECT_TRUE(found); |
|
if (!found) { |
|
if (first_timestamp) { |
|
GTEST_LOG_(INFO) << "first = " << first_timestamp << "\n"; |
|
} |
|
if (second_timestamp) { |
|
GTEST_LOG_(INFO) << "second = " << second_timestamp << "\n"; |
|
} |
|
if (last_timestamp) { |
|
GTEST_LOG_(INFO) << "last = " << last_timestamp << "\n"; |
|
} |
|
} |
|
free(last_timestamp); |
|
last_timestamp = NULL; |
|
free(first_timestamp); |
|
free(second_timestamp); |
|
|
|
EXPECT_TRUE(first_timestamp == NULL); |
|
EXPECT_TRUE(second_timestamp == NULL); |
|
EXPECT_LE(count, second_count); |
|
EXPECT_LE(count, last_timestamp_count); |
|
} |
|
|
|
TEST(logcat, tail_time) { |
|
do_tail_time(logcat_executable " -v long -v nsec -b all"); |
|
} |
|
|
|
TEST(logcat, tail_time_epoch) { |
|
do_tail_time(logcat_executable " -v long -v nsec -v epoch -b all"); |
|
} |
|
|
|
TEST(logcat, End_to_End) { |
|
pid_t pid = getpid(); |
|
|
|
log_time ts(CLOCK_MONOTONIC); |
|
|
|
ASSERT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts))); |
|
|
|
FILE* fp; |
|
logcat_define(ctx); |
|
ASSERT_TRUE(NULL != |
|
(fp = logcat_popen(ctx, logcat_executable |
|
" -v brief -b events -t 100 2>/dev/null"))); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
int count = 0; |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
int p; |
|
unsigned long long t; |
|
|
|
if ((2 != sscanf(buffer, "I/[0] ( %d): %llu", &p, &t)) || |
|
(p != pid)) { |
|
continue; |
|
} |
|
|
|
log_time tx((const char*)&t); |
|
if (ts == tx) { |
|
++count; |
|
} |
|
} |
|
|
|
logcat_pclose(ctx, fp); |
|
|
|
ASSERT_EQ(1, count); |
|
} |
|
|
|
TEST(logcat, End_to_End_multitude) { |
|
pid_t pid = getpid(); |
|
|
|
log_time ts(CLOCK_MONOTONIC); |
|
|
|
ASSERT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts))); |
|
|
|
FILE* fp[256]; // does this count as a multitude! |
|
memset(fp, 0, sizeof(fp)); |
|
logcat_define(ctx[sizeof(fp) / sizeof(fp[0])]); |
|
size_t num = 0; |
|
do { |
|
EXPECT_TRUE(NULL != |
|
(fp[num] = logcat_popen(ctx[num], logcat_executable |
|
" -v brief -b events -t 100"))); |
|
if (!fp[num]) { |
|
fprintf(stderr, |
|
"WARNING: limiting to %zu simultaneous logcat operations\n", |
|
num); |
|
break; |
|
} |
|
} while (++num < sizeof(fp) / sizeof(fp[0])); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
size_t count = 0; |
|
|
|
for (size_t idx = 0; idx < sizeof(fp) / sizeof(fp[0]); ++idx) { |
|
if (!fp[idx]) break; |
|
while (fgets(buffer, sizeof(buffer), fp[idx])) { |
|
int p; |
|
unsigned long long t; |
|
|
|
if ((2 != sscanf(buffer, "I/[0] ( %d): %llu", &p, &t)) || |
|
(p != pid)) { |
|
continue; |
|
} |
|
|
|
log_time tx((const char*)&t); |
|
if (ts == tx) { |
|
++count; |
|
} |
|
} |
|
|
|
logcat_pclose(ctx[idx], fp[idx]); |
|
} |
|
|
|
ASSERT_EQ(num, count); |
|
} |
|
|
|
static int get_groups(const char* cmd) { |
|
FILE* fp; |
|
logcat_define(ctx); |
|
|
|
// NB: crash log only available in user space |
|
EXPECT_TRUE(NULL != (fp = logcat_popen(ctx, cmd))); |
|
|
|
if (fp == NULL) { |
|
return 0; |
|
} |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
int count = 0; |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
int size, consumed, max, payload; |
|
char size_mult[3], consumed_mult[3]; |
|
long full_size, full_consumed; |
|
|
|
size = consumed = max = payload = 0; |
|
// NB: crash log can be very small, not hit a Kb of consumed space |
|
// doubly lucky we are not including it. |
|
if (6 != sscanf(buffer, |
|
"%*s ring buffer is %d%2s (%d%2s consumed)," |
|
" max entry is %db, max payload is %db", |
|
&size, size_mult, &consumed, consumed_mult, &max, |
|
&payload)) { |
|
fprintf(stderr, "WARNING: Parse error: %s", buffer); |
|
continue; |
|
} |
|
full_size = size; |
|
switch (size_mult[0]) { |
|
case 'G': |
|
full_size *= 1024; |
|
/* FALLTHRU */ |
|
case 'M': |
|
full_size *= 1024; |
|
/* FALLTHRU */ |
|
case 'K': |
|
full_size *= 1024; |
|
/* FALLTHRU */ |
|
case 'b': |
|
break; |
|
} |
|
full_consumed = consumed; |
|
switch (consumed_mult[0]) { |
|
case 'G': |
|
full_consumed *= 1024; |
|
/* FALLTHRU */ |
|
case 'M': |
|
full_consumed *= 1024; |
|
/* FALLTHRU */ |
|
case 'K': |
|
full_consumed *= 1024; |
|
/* FALLTHRU */ |
|
case 'b': |
|
break; |
|
} |
|
EXPECT_GT((full_size * 9) / 4, full_consumed); |
|
EXPECT_GT(full_size, max); |
|
EXPECT_GT(max, payload); |
|
|
|
if ((((full_size * 9) / 4) >= full_consumed) && (full_size > max) && |
|
(max > payload)) { |
|
++count; |
|
} |
|
} |
|
|
|
logcat_pclose(ctx, fp); |
|
|
|
return count; |
|
} |
|
|
|
TEST(logcat, get_size) { |
|
ASSERT_EQ(4, get_groups(logcat_executable |
|
" -v brief -b radio -b events -b system -b " |
|
"main -g 2>/dev/null")); |
|
} |
|
|
|
// duplicate test for get_size, but use comma-separated list of buffers |
|
TEST(logcat, multiple_buffer) { |
|
ASSERT_EQ( |
|
4, get_groups(logcat_executable |
|
" -v brief -b radio,events,system,main -g 2>/dev/null")); |
|
} |
|
|
|
TEST(logcat, bad_buffer) { |
|
ASSERT_EQ(0, |
|
get_groups( |
|
logcat_executable |
|
" -v brief -b radio,events,bogo,system,main -g 2>/dev/null")); |
|
} |
|
|
|
#ifndef logcat |
|
static void caught_blocking(int signum) { |
|
unsigned long long v = 0xDEADBEEFA55A0000ULL; |
|
|
|
v += getpid() & 0xFFFF; |
|
if (signum == 0) ++v; |
|
|
|
LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v))); |
|
} |
|
|
|
TEST(logcat, blocking) { |
|
FILE* fp; |
|
unsigned long long v = 0xDEADBEEFA55F0000ULL; |
|
|
|
pid_t pid = getpid(); |
|
|
|
v += pid & 0xFFFF; |
|
|
|
LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v))); |
|
|
|
v &= 0xFFFFFFFFFFFAFFFFULL; |
|
|
|
ASSERT_TRUE( |
|
NULL != |
|
(fp = popen("( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&" |
|
" logcat -v brief -b events 2>&1", |
|
"r"))); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
int count = 0; |
|
|
|
int signals = 0; |
|
|
|
signal(SIGALRM, caught_blocking); |
|
alarm(2); |
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
if (!strncmp(buffer, "DONE", 4)) { |
|
break; |
|
} |
|
|
|
++count; |
|
|
|
int p; |
|
unsigned long long l; |
|
|
|
if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l)) || (p != pid)) { |
|
continue; |
|
} |
|
|
|
if (l == v) { |
|
++signals; |
|
break; |
|
} |
|
} |
|
alarm(0); |
|
signal(SIGALRM, SIG_DFL); |
|
|
|
// Generate SIGPIPE |
|
fclose(fp); |
|
caught_blocking(0); |
|
|
|
pclose(fp); |
|
|
|
EXPECT_GE(count, 2); |
|
|
|
EXPECT_EQ(signals, 1); |
|
} |
|
|
|
static void caught_blocking_tail(int signum) { |
|
unsigned long long v = 0xA55ADEADBEEF0000ULL; |
|
|
|
v += getpid() & 0xFFFF; |
|
if (signum == 0) ++v; |
|
|
|
LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v))); |
|
} |
|
|
|
TEST(logcat, blocking_tail) { |
|
FILE* fp; |
|
unsigned long long v = 0xA55FDEADBEEF0000ULL; |
|
|
|
pid_t pid = getpid(); |
|
|
|
v += pid & 0xFFFF; |
|
|
|
LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v))); |
|
|
|
v &= 0xFFFAFFFFFFFFFFFFULL; |
|
|
|
ASSERT_TRUE( |
|
NULL != |
|
(fp = popen("( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&" |
|
" logcat -v brief -b events -T 5 2>&1", |
|
"r"))); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
int count = 0; |
|
|
|
int signals = 0; |
|
|
|
signal(SIGALRM, caught_blocking_tail); |
|
alarm(2); |
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
if (!strncmp(buffer, "DONE", 4)) { |
|
break; |
|
} |
|
|
|
++count; |
|
|
|
int p; |
|
unsigned long long l; |
|
|
|
if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l)) || (p != pid)) { |
|
continue; |
|
} |
|
|
|
if (l == v) { |
|
if (count >= 5) { |
|
++signals; |
|
} |
|
break; |
|
} |
|
} |
|
alarm(0); |
|
signal(SIGALRM, SIG_DFL); |
|
|
|
// Generate SIGPIPE |
|
fclose(fp); |
|
caught_blocking_tail(0); |
|
|
|
pclose(fp); |
|
|
|
EXPECT_GE(count, 2); |
|
|
|
EXPECT_EQ(signals, 1); |
|
} |
|
#endif |
|
|
|
// meant to be handed to ASSERT_FALSE / EXPECT_FALSE to expand the message |
|
static testing::AssertionResult IsFalse(int ret, const char* command) { |
|
return ret ? (testing::AssertionSuccess() |
|
<< "ret=" << ret << " command=\"" << command << "\"") |
|
: testing::AssertionFailure(); |
|
} |
|
|
|
TEST(logcat, logrotate) { |
|
static const char form[] = "/data/local/tmp/logcat.logrotate.XXXXXX"; |
|
char buf[sizeof(form)]; |
|
ASSERT_TRUE(NULL != mkdtemp(strcpy(buf, form))); |
|
|
|
static const char comm[] = logcat_executable |
|
" -b radio -b events -b system -b main" |
|
" -d -f %s/log.txt -n 7 -r 1"; |
|
char command[sizeof(buf) + sizeof(comm)]; |
|
snprintf(command, sizeof(command), comm, buf); |
|
|
|
int ret; |
|
EXPECT_FALSE(IsFalse(ret = logcat_system(command), command)); |
|
if (!ret) { |
|
snprintf(command, sizeof(command), "ls -s %s 2>/dev/null", buf); |
|
|
|
FILE* fp; |
|
EXPECT_TRUE(NULL != (fp = popen(command, "r"))); |
|
if (fp) { |
|
char buffer[BIG_BUFFER]; |
|
int count = 0; |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
static const char total[] = "total "; |
|
int num; |
|
char c; |
|
|
|
if ((2 == sscanf(buffer, "%d log.tx%c", &num, &c)) && |
|
(num <= 40)) { |
|
++count; |
|
} else if (strncmp(buffer, total, sizeof(total) - 1)) { |
|
fprintf(stderr, "WARNING: Parse error: %s", buffer); |
|
} |
|
} |
|
pclose(fp); |
|
if ((count != 7) && (count != 8)) { |
|
fprintf(stderr, "count=%d\n", count); |
|
} |
|
EXPECT_TRUE(count == 7 || count == 8); |
|
} |
|
} |
|
snprintf(command, sizeof(command), "rm -rf %s", buf); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
} |
|
|
|
TEST(logcat, logrotate_suffix) { |
|
static const char tmp_out_dir_form[] = |
|
"/data/local/tmp/logcat.logrotate.XXXXXX"; |
|
char tmp_out_dir[sizeof(tmp_out_dir_form)]; |
|
ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form))); |
|
|
|
static const char logcat_cmd[] = logcat_executable |
|
" -b radio -b events -b system -b main" |
|
" -d -f %s/log.txt -n 10 -r 1"; |
|
char command[sizeof(tmp_out_dir) + sizeof(logcat_cmd)]; |
|
snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir); |
|
|
|
int ret; |
|
EXPECT_FALSE(IsFalse(ret = logcat_system(command), command)); |
|
if (!ret) { |
|
snprintf(command, sizeof(command), "ls %s 2>/dev/null", tmp_out_dir); |
|
|
|
FILE* fp; |
|
EXPECT_TRUE(NULL != (fp = popen(command, "r"))); |
|
char buffer[BIG_BUFFER]; |
|
int log_file_count = 0; |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
static const char rotated_log_filename_prefix[] = "log.txt."; |
|
static const size_t rotated_log_filename_prefix_len = |
|
strlen(rotated_log_filename_prefix); |
|
static const char log_filename[] = "log.txt"; |
|
|
|
if (!strncmp(buffer, rotated_log_filename_prefix, |
|
rotated_log_filename_prefix_len)) { |
|
// Rotated file should have form log.txt.## |
|
char* rotated_log_filename_suffix = |
|
buffer + rotated_log_filename_prefix_len; |
|
char* endptr; |
|
const long int suffix_value = |
|
strtol(rotated_log_filename_suffix, &endptr, 10); |
|
EXPECT_EQ(rotated_log_filename_suffix + 2, endptr); |
|
EXPECT_LE(suffix_value, 10); |
|
EXPECT_GT(suffix_value, 0); |
|
++log_file_count; |
|
continue; |
|
} |
|
|
|
if (!strncmp(buffer, log_filename, strlen(log_filename))) { |
|
++log_file_count; |
|
continue; |
|
} |
|
|
|
fprintf(stderr, "ERROR: Unexpected file: %s", buffer); |
|
ADD_FAILURE(); |
|
} |
|
pclose(fp); |
|
EXPECT_EQ(log_file_count, 11); |
|
} |
|
snprintf(command, sizeof(command), "rm -rf %s", tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
} |
|
|
|
TEST(logcat, logrotate_continue) { |
|
static const char tmp_out_dir_form[] = |
|
"/data/local/tmp/logcat.logrotate.XXXXXX"; |
|
char tmp_out_dir[sizeof(tmp_out_dir_form)]; |
|
ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form))); |
|
|
|
static const char log_filename[] = "log.txt"; |
|
static const char logcat_cmd[] = |
|
logcat_executable " -b all -v nsec -d -f %s/%s -n 256 -r 1024"; |
|
static const char cleanup_cmd[] = "rm -rf %s"; |
|
char command[sizeof(tmp_out_dir) + sizeof(logcat_cmd) + sizeof(log_filename)]; |
|
snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir, log_filename); |
|
|
|
int ret; |
|
EXPECT_FALSE(IsFalse(ret = logcat_system(command), command)); |
|
if (ret) { |
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
return; |
|
} |
|
FILE* fp; |
|
snprintf(command, sizeof(command), "%s/%s", tmp_out_dir, log_filename); |
|
EXPECT_TRUE(NULL != ((fp = fopen(command, "r")))); |
|
if (!fp) { |
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
return; |
|
} |
|
char* line = NULL; |
|
char* last_line = |
|
NULL; // this line is allowed to stutter, one-line overlap |
|
char* second_last_line = NULL; // should never stutter |
|
char* first_line = NULL; // help diagnose failure? |
|
size_t len = 0; |
|
while (getline(&line, &len, fp) != -1) { |
|
if (!first_line) { |
|
first_line = line; |
|
line = NULL; |
|
continue; |
|
} |
|
free(second_last_line); |
|
second_last_line = last_line; |
|
last_line = line; |
|
line = NULL; |
|
} |
|
fclose(fp); |
|
free(line); |
|
if (second_last_line == NULL) { |
|
fprintf(stderr, "No second to last line, using last, test may fail\n"); |
|
second_last_line = last_line; |
|
last_line = NULL; |
|
} |
|
free(last_line); |
|
EXPECT_TRUE(NULL != second_last_line); |
|
if (!second_last_line) { |
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
free(first_line); |
|
return; |
|
} |
|
// re-run the command, it should only add a few lines more content if it |
|
// continues where it left off. |
|
snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir, log_filename); |
|
EXPECT_FALSE(IsFalse(ret = logcat_system(command), command)); |
|
if (ret) { |
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
free(second_last_line); |
|
free(first_line); |
|
return; |
|
} |
|
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir), |
|
closedir); |
|
EXPECT_NE(nullptr, dir); |
|
if (!dir) { |
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
free(second_last_line); |
|
free(first_line); |
|
return; |
|
} |
|
struct dirent* entry; |
|
unsigned count = 0; |
|
while ((entry = readdir(dir.get()))) { |
|
if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) { |
|
continue; |
|
} |
|
snprintf(command, sizeof(command), "%s/%s", tmp_out_dir, entry->d_name); |
|
EXPECT_TRUE(NULL != ((fp = fopen(command, "r")))); |
|
if (!fp) { |
|
fprintf(stderr, "%s ?\n", command); |
|
continue; |
|
} |
|
line = NULL; |
|
size_t number = 0; |
|
while (getline(&line, &len, fp) != -1) { |
|
++number; |
|
if (!strcmp(line, second_last_line)) { |
|
EXPECT_TRUE(++count <= 1); |
|
fprintf(stderr, "%s(%zu):\n", entry->d_name, number); |
|
} |
|
} |
|
fclose(fp); |
|
free(line); |
|
unlink(command); |
|
} |
|
if (count > 1) { |
|
char* brk = strpbrk(second_last_line, "\r\n"); |
|
if (!brk) brk = second_last_line + strlen(second_last_line); |
|
fprintf(stderr, "\"%.*s\" occurred %u times\n", |
|
(int)(brk - second_last_line), second_last_line, count); |
|
if (first_line) { |
|
brk = strpbrk(first_line, "\r\n"); |
|
if (!brk) brk = first_line + strlen(first_line); |
|
fprintf(stderr, "\"%.*s\" was first line, fault?\n", |
|
(int)(brk - first_line), first_line); |
|
} |
|
} |
|
free(second_last_line); |
|
free(first_line); |
|
|
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
} |
|
|
|
TEST(logcat, logrotate_clear) { |
|
static const char tmp_out_dir_form[] = |
|
"/data/local/tmp/logcat.logrotate.XXXXXX"; |
|
char tmp_out_dir[sizeof(tmp_out_dir_form)]; |
|
ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form))); |
|
|
|
static const char log_filename[] = "log.txt"; |
|
static const unsigned num_val = 32; |
|
static const char logcat_cmd[] = |
|
logcat_executable " -b all -d -f %s/%s -n %d -r 1"; |
|
static const char clear_cmd[] = " -c"; |
|
static const char cleanup_cmd[] = "rm -rf %s"; |
|
char command[sizeof(tmp_out_dir) + sizeof(logcat_cmd) + |
|
sizeof(log_filename) + sizeof(clear_cmd) + 32]; |
|
|
|
// Run command with all data |
|
{ |
|
snprintf(command, sizeof(command) - sizeof(clear_cmd), logcat_cmd, |
|
tmp_out_dir, log_filename, num_val); |
|
|
|
int ret; |
|
EXPECT_FALSE(IsFalse(ret = logcat_system(command), command)); |
|
if (ret) { |
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
return; |
|
} |
|
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir), |
|
closedir); |
|
EXPECT_NE(nullptr, dir); |
|
if (!dir) { |
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
return; |
|
} |
|
struct dirent* entry; |
|
unsigned count = 0; |
|
while ((entry = readdir(dir.get()))) { |
|
if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) { |
|
continue; |
|
} |
|
++count; |
|
} |
|
EXPECT_EQ(count, num_val + 1); |
|
} |
|
|
|
{ |
|
// Now with -c option tacked onto the end |
|
strcat(command, clear_cmd); |
|
|
|
int ret; |
|
EXPECT_FALSE(IsFalse(ret = logcat_system(command), command)); |
|
if (ret) { |
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(system(command)); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
return; |
|
} |
|
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir), |
|
closedir); |
|
EXPECT_NE(nullptr, dir); |
|
if (!dir) { |
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
return; |
|
} |
|
struct dirent* entry; |
|
unsigned count = 0; |
|
while ((entry = readdir(dir.get()))) { |
|
if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) { |
|
continue; |
|
} |
|
fprintf(stderr, "Found %s/%s!!!\n", tmp_out_dir, entry->d_name); |
|
++count; |
|
} |
|
EXPECT_EQ(count, 0U); |
|
} |
|
|
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
} |
|
|
|
static int logrotate_count_id(const char* logcat_cmd, const char* tmp_out_dir) { |
|
static const char log_filename[] = "log.txt"; |
|
char command[strlen(tmp_out_dir) + strlen(logcat_cmd) + |
|
strlen(log_filename) + 32]; |
|
|
|
snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir, log_filename); |
|
|
|
int ret = logcat_system(command); |
|
if (ret) { |
|
fprintf(stderr, "system(\"%s\")=%d", command, ret); |
|
return -1; |
|
} |
|
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir), |
|
closedir); |
|
if (!dir) { |
|
fprintf(stderr, "opendir(\"%s\") failed", tmp_out_dir); |
|
return -1; |
|
} |
|
struct dirent* entry; |
|
int count = 0; |
|
while ((entry = readdir(dir.get()))) { |
|
if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) { |
|
continue; |
|
} |
|
++count; |
|
} |
|
return count; |
|
} |
|
|
|
TEST(logcat, logrotate_id) { |
|
static const char logcat_cmd[] = |
|
logcat_executable " -b all -d -f %s/%s -n 32 -r 1 --id=test"; |
|
static const char logcat_short_cmd[] = |
|
logcat_executable " -b all -t 10 -f %s/%s -n 32 -r 1 --id=test"; |
|
static const char tmp_out_dir_form[] = |
|
"/data/local/tmp/logcat.logrotate.XXXXXX"; |
|
static const char log_filename[] = "log.txt"; |
|
char tmp_out_dir[strlen(tmp_out_dir_form) + 1]; |
|
ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form))); |
|
|
|
EXPECT_EQ(logrotate_count_id(logcat_cmd, tmp_out_dir), 34); |
|
EXPECT_EQ(logrotate_count_id(logcat_short_cmd, tmp_out_dir), 34); |
|
|
|
char id_file[strlen(tmp_out_dir_form) + strlen(log_filename) + 5]; |
|
snprintf(id_file, sizeof(id_file), "%s/%s.id", tmp_out_dir, log_filename); |
|
if (getuid() != 0) { |
|
chmod(id_file, 0); |
|
EXPECT_EQ(logrotate_count_id(logcat_short_cmd, tmp_out_dir), 34); |
|
} |
|
unlink(id_file); |
|
EXPECT_EQ(logrotate_count_id(logcat_short_cmd, tmp_out_dir), 34); |
|
|
|
FILE* fp = fopen(id_file, "w"); |
|
if (fp) { |
|
fprintf(fp, "not_a_test"); |
|
fclose(fp); |
|
} |
|
if (getuid() != 0) { |
|
chmod(id_file, |
|
0); // API to preserve content even with signature change |
|
ASSERT_EQ(34, logrotate_count_id(logcat_short_cmd, tmp_out_dir)); |
|
chmod(id_file, 0600); |
|
} |
|
|
|
int new_signature; |
|
EXPECT_GE( |
|
(new_signature = logrotate_count_id(logcat_short_cmd, tmp_out_dir)), 2); |
|
EXPECT_LT(new_signature, 34); |
|
|
|
static const char cleanup_cmd[] = "rm -rf %s"; |
|
char command[strlen(cleanup_cmd) + strlen(tmp_out_dir_form)]; |
|
snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir); |
|
EXPECT_FALSE(IsFalse(system(command), command)); |
|
} |
|
|
|
TEST(logcat, logrotate_nodir) { |
|
// expect logcat to error out on writing content and not exit(0) for nodir |
|
static const char command[] = logcat_executable |
|
" -b all -d" |
|
" -f /das/nein/gerfingerpoken/logcat/log.txt" |
|
" -n 256 -r 1024"; |
|
EXPECT_FALSE(IsFalse(0 == logcat_system(command), command)); |
|
} |
|
|
|
#ifndef logcat |
|
static void caught_blocking_clear(int signum) { |
|
unsigned long long v = 0xDEADBEEFA55C0000ULL; |
|
|
|
v += getpid() & 0xFFFF; |
|
if (signum == 0) ++v; |
|
|
|
LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v))); |
|
} |
|
|
|
TEST(logcat, blocking_clear) { |
|
FILE* fp; |
|
unsigned long long v = 0xDEADBEEFA55C0000ULL; |
|
|
|
pid_t pid = getpid(); |
|
|
|
v += pid & 0xFFFF; |
|
|
|
// This test is racey; an event occurs between clear and dump. |
|
// We accept that we will get a false positive, but never a false negative. |
|
ASSERT_TRUE( |
|
NULL != |
|
(fp = popen("( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&" |
|
" logcat -b events -c 2>&1 ;" |
|
" logcat -b events -g 2>&1 ;" |
|
" logcat -v brief -b events 2>&1", |
|
"r"))); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
int count = 0; |
|
int minus_g = 0; |
|
|
|
int signals = 0; |
|
|
|
signal(SIGALRM, caught_blocking_clear); |
|
alarm(2); |
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
if (!strncmp(buffer, "clearLog: ", strlen("clearLog: "))) { |
|
fprintf(stderr, "WARNING: Test lacks permission to run :-(\n"); |
|
count = signals = 1; |
|
break; |
|
} |
|
if (!strncmp(buffer, "failed to clear", strlen("failed to clear"))) { |
|
fprintf(stderr, "WARNING: Test lacks permission to run :-(\n"); |
|
count = signals = 1; |
|
break; |
|
} |
|
|
|
if (!strncmp(buffer, "DONE", 4)) { |
|
break; |
|
} |
|
|
|
int size, consumed, max, payload; |
|
char size_mult[3], consumed_mult[3]; |
|
size = consumed = max = payload = 0; |
|
if (6 == sscanf(buffer, |
|
"events: ring buffer is %d%2s (%d%2s consumed)," |
|
" max entry is %db, max payload is %db", |
|
&size, size_mult, &consumed, consumed_mult, &max, |
|
&payload)) { |
|
long full_size = size, full_consumed = consumed; |
|
|
|
switch (size_mult[0]) { |
|
case 'G': |
|
full_size *= 1024; |
|
/* FALLTHRU */ |
|
case 'M': |
|
full_size *= 1024; |
|
/* FALLTHRU */ |
|
case 'K': |
|
full_size *= 1024; |
|
/* FALLTHRU */ |
|
case 'b': |
|
break; |
|
} |
|
switch (consumed_mult[0]) { |
|
case 'G': |
|
full_consumed *= 1024; |
|
/* FALLTHRU */ |
|
case 'M': |
|
full_consumed *= 1024; |
|
/* FALLTHRU */ |
|
case 'K': |
|
full_consumed *= 1024; |
|
/* FALLTHRU */ |
|
case 'b': |
|
break; |
|
} |
|
EXPECT_GT(full_size, full_consumed); |
|
EXPECT_GT(full_size, max); |
|
EXPECT_GT(max, payload); |
|
EXPECT_GT(max, full_consumed); |
|
|
|
++minus_g; |
|
continue; |
|
} |
|
|
|
++count; |
|
|
|
int p; |
|
unsigned long long l; |
|
|
|
if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l)) || (p != pid)) { |
|
continue; |
|
} |
|
|
|
if (l == v) { |
|
if (count > 1) { |
|
fprintf(stderr, "WARNING: Possible false positive\n"); |
|
} |
|
++signals; |
|
break; |
|
} |
|
} |
|
alarm(0); |
|
signal(SIGALRM, SIG_DFL); |
|
|
|
// Generate SIGPIPE |
|
fclose(fp); |
|
caught_blocking_clear(0); |
|
|
|
pclose(fp); |
|
|
|
EXPECT_GE(count, 1); |
|
EXPECT_EQ(minus_g, 1); |
|
|
|
EXPECT_EQ(signals, 1); |
|
} |
|
#endif |
|
|
|
static bool get_white_black(char** list) { |
|
FILE* fp; |
|
logcat_define(ctx); |
|
|
|
fp = logcat_popen(ctx, logcat_executable " -p 2>/dev/null"); |
|
if (fp == NULL) { |
|
fprintf(stderr, "ERROR: logcat -p 2>/dev/null\n"); |
|
return false; |
|
} |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
char* hold = *list; |
|
char* buf = buffer; |
|
while (isspace(*buf)) { |
|
++buf; |
|
} |
|
char* end = buf + strlen(buf); |
|
while (isspace(*--end) && (end >= buf)) { |
|
*end = '\0'; |
|
} |
|
if (end < buf) { |
|
continue; |
|
} |
|
if (hold) { |
|
asprintf(list, "%s %s", hold, buf); |
|
free(hold); |
|
} else { |
|
asprintf(list, "%s", buf); |
|
} |
|
} |
|
logcat_pclose(ctx, fp); |
|
return *list != NULL; |
|
} |
|
|
|
static bool set_white_black(const char* list) { |
|
FILE* fp; |
|
logcat_define(ctx); |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
snprintf(buffer, sizeof(buffer), logcat_executable " -P '%s' 2>&1", |
|
list ? list : ""); |
|
fp = logcat_popen(ctx, buffer); |
|
if (fp == NULL) { |
|
fprintf(stderr, "ERROR: %s\n", buffer); |
|
return false; |
|
} |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
char* buf = buffer; |
|
while (isspace(*buf)) { |
|
++buf; |
|
} |
|
char* end = buf + strlen(buf); |
|
while ((end > buf) && isspace(*--end)) { |
|
*end = '\0'; |
|
} |
|
if (end <= buf) { |
|
continue; |
|
} |
|
fprintf(stderr, "%s\n", buf); |
|
logcat_pclose(ctx, fp); |
|
return false; |
|
} |
|
return logcat_pclose(ctx, fp) == 0; |
|
} |
|
|
|
TEST(logcat, white_black_adjust) { |
|
char* list = NULL; |
|
char* adjust = NULL; |
|
|
|
get_white_black(&list); |
|
|
|
static const char adjustment[] = "~! 300/20 300/25 2000 ~1000/5 ~1000/30"; |
|
ASSERT_EQ(true, set_white_black(adjustment)); |
|
ASSERT_EQ(true, get_white_black(&adjust)); |
|
EXPECT_STREQ(adjustment, adjust); |
|
free(adjust); |
|
adjust = NULL; |
|
|
|
static const char adjustment2[] = "300/20 300/21 2000 ~1000"; |
|
ASSERT_EQ(true, set_white_black(adjustment2)); |
|
ASSERT_EQ(true, get_white_black(&adjust)); |
|
EXPECT_STREQ(adjustment2, adjust); |
|
free(adjust); |
|
adjust = NULL; |
|
|
|
ASSERT_EQ(true, set_white_black(list)); |
|
get_white_black(&adjust); |
|
EXPECT_STREQ(list ? list : "", adjust ? adjust : ""); |
|
free(adjust); |
|
adjust = NULL; |
|
|
|
free(list); |
|
list = NULL; |
|
} |
|
|
|
TEST(logcat, regex) { |
|
FILE* fp; |
|
logcat_define(ctx); |
|
int count = 0; |
|
|
|
char buffer[BIG_BUFFER]; |
|
#define logcat_regex_prefix ___STRING(logcat) "_test" |
|
|
|
snprintf(buffer, sizeof(buffer), |
|
logcat_executable " --pid %d -d -e " logcat_regex_prefix "_a+b", |
|
getpid()); |
|
|
|
LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, logcat_regex_prefix, |
|
logcat_regex_prefix "_ab")); |
|
LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, logcat_regex_prefix, |
|
logcat_regex_prefix "_b")); |
|
LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, logcat_regex_prefix, |
|
logcat_regex_prefix "_aaaab")); |
|
LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, logcat_regex_prefix, |
|
logcat_regex_prefix "_aaaa")); |
|
// Let the logs settle |
|
rest(); |
|
|
|
ASSERT_TRUE(NULL != (fp = logcat_popen(ctx, buffer))); |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
if (!strncmp(begin, buffer, sizeof(begin) - 1)) { |
|
continue; |
|
} |
|
|
|
EXPECT_TRUE(strstr(buffer, logcat_regex_prefix "_") != NULL); |
|
|
|
count++; |
|
} |
|
|
|
logcat_pclose(ctx, fp); |
|
|
|
ASSERT_EQ(2, count); |
|
} |
|
|
|
TEST(logcat, maxcount) { |
|
FILE* fp; |
|
logcat_define(ctx); |
|
int count = 0; |
|
|
|
char buffer[BIG_BUFFER]; |
|
|
|
snprintf(buffer, sizeof(buffer), |
|
logcat_executable " --pid %d -d --max-count 3", getpid()); |
|
|
|
LOG_FAILURE_RETRY( |
|
__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test")); |
|
LOG_FAILURE_RETRY( |
|
__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test")); |
|
LOG_FAILURE_RETRY( |
|
__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test")); |
|
LOG_FAILURE_RETRY( |
|
__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test")); |
|
|
|
rest(); |
|
|
|
ASSERT_TRUE(NULL != (fp = logcat_popen(ctx, buffer))); |
|
|
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
if (!strncmp(begin, buffer, sizeof(begin) - 1)) { |
|
continue; |
|
} |
|
|
|
count++; |
|
} |
|
|
|
logcat_pclose(ctx, fp); |
|
|
|
ASSERT_EQ(3, count); |
|
} |
|
|
|
static bool End_to_End(const char* tag, const char* fmt, ...) |
|
#if defined(__GNUC__) |
|
__attribute__((__format__(printf, 2, 3))) |
|
#endif |
|
; |
|
|
|
static bool End_to_End(const char* tag, const char* fmt, ...) { |
|
logcat_define(ctx); |
|
FILE* fp = logcat_popen(ctx, logcat_executable |
|
" -v brief" |
|
" -b events" |
|
" -v descriptive" |
|
" -t 100" |
|
" 2>/dev/null"); |
|
if (!fp) { |
|
fprintf(stderr, "End_to_End: popen failed"); |
|
return false; |
|
} |
|
|
|
char buffer[BIG_BUFFER]; |
|
va_list ap; |
|
|
|
va_start(ap, fmt); |
|
vsnprintf(buffer, sizeof(buffer), fmt, ap); |
|
va_end(ap); |
|
|
|
char* str = NULL; |
|
asprintf(&str, "I/%s ( %%d):%%c%s%%c", tag, buffer); |
|
std::string expect(str); |
|
free(str); |
|
|
|
int count = 0; |
|
pid_t pid = getpid(); |
|
std::string lastMatch; |
|
int maxMatch = 1; |
|
while (fgets(buffer, sizeof(buffer), fp)) { |
|
char space; |
|
char newline; |
|
int p; |
|
int ret = sscanf(buffer, expect.c_str(), &p, &space, &newline); |
|
if ((ret == 3) && (p == pid) && (space == ' ') && (newline == '\n')) { |
|
++count; |
|
} else if ((ret >= maxMatch) && (p == pid) && (count == 0)) { |
|
lastMatch = buffer; |
|
maxMatch = ret; |
|
} |
|
} |
|
|
|
logcat_pclose(ctx, fp); |
|
|
|
if ((count == 0) && (lastMatch.length() > 0)) { |
|
// Help us pinpoint where things went wrong ... |
|
fprintf(stderr, "Closest match for\n %s\n is\n %s", |
|
expect.c_str(), lastMatch.c_str()); |
|
} else if (count > 3) { |
|
fprintf(stderr, "Too many matches (%d) for %s\n", count, expect.c_str()); |
|
} |
|
|
|
// Three different known tests, we can see pollution from the others |
|
return count && (count <= 3); |
|
} |
|
|
|
TEST(logcat, descriptive) { |
|
struct tag { |
|
uint32_t tagNo; |
|
const char* tagStr; |
|
}; |
|
int ret; |
|
|
|
{ |
|
static const struct tag hhgtg = { 42, "answer" }; |
|
android_log_event_list ctx(hhgtg.tagNo); |
|
static const char theAnswer[] = "what is five by seven"; |
|
ctx << theAnswer; |
|
// crafted to rest at least once after, and rest between retries. |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE( |
|
End_to_End(hhgtg.tagStr, "to life the universe etc=%s", theAnswer)); |
|
} |
|
|
|
{ |
|
static const struct tag sync = { 2720, "sync" }; |
|
static const char id[] = ___STRING(logcat) ".descriptive-sync"; |
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << id << (int32_t)42 << (int32_t)-1 << (int32_t)0; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, |
|
"[id=%s,event=42,source=-1,account=0]", id)); |
|
} |
|
|
|
// Partial match to description |
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << id << (int32_t)43 << (int64_t)-1 << (int32_t)0; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "[id=%s,event=43,-1,0]", id)); |
|
} |
|
|
|
// Negative Test of End_to_End, ensure it is working |
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << id << (int32_t)44 << (int32_t)-1 << (int64_t)0; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
fprintf(stderr, "Expect a \"Closest match\" message\n"); |
|
EXPECT_FALSE(End_to_End( |
|
sync.tagStr, "[id=%s,event=44,source=-1,account=0]", id)); |
|
} |
|
} |
|
|
|
{ |
|
static const struct tag sync = { 2747, "contacts_aggregation" }; |
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint64_t)30 << (int32_t)2; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE( |
|
End_to_End(sync.tagStr, "[aggregation time=30ms,count=2]")); |
|
} |
|
|
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint64_t)31570 << (int32_t)911; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE( |
|
End_to_End(sync.tagStr, "[aggregation time=31.57s,count=911]")); |
|
} |
|
} |
|
|
|
{ |
|
static const struct tag sync = { 75000, "sqlite_mem_alarm_current" }; |
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint32_t)512; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "current=512B")); |
|
} |
|
|
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint32_t)3072; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "current=3KB")); |
|
} |
|
|
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint32_t)2097152; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "current=2MB")); |
|
} |
|
|
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint32_t)2097153; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "current=2097153B")); |
|
} |
|
|
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint32_t)1073741824; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "current=1GB")); |
|
} |
|
|
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint32_t)3221225472; // 3MB, but on purpose overflowed |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "current=-1GB")); |
|
} |
|
} |
|
|
|
{ |
|
static const struct tag sync = { 27501, "notification_panel_hidden" }; |
|
android_log_event_list ctx(sync.tagNo); |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "")); |
|
} |
|
|
|
{ |
|
// Invent new entries because existing can not serve |
|
EventTagMap* map = android_openEventTagMap(nullptr); |
|
ASSERT_TRUE(nullptr != map); |
|
static const char name[] = ___STRING(logcat) ".descriptive-monotonic"; |
|
int myTag = android_lookupEventTagNum(map, name, "(new|1|s)", |
|
ANDROID_LOG_UNKNOWN); |
|
android_closeEventTagMap(map); |
|
ASSERT_NE(-1, myTag); |
|
|
|
const struct tag sync = { (uint32_t)myTag, name }; |
|
|
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint32_t)7; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "new=7s")); |
|
} |
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint32_t)62; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "new=1:02")); |
|
} |
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint32_t)3673; |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "new=1:01:13")); |
|
} |
|
{ |
|
android_log_event_list ctx(sync.tagNo); |
|
ctx << (uint32_t)(86400 + 7200 + 180 + 58); |
|
for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write(); |
|
EXPECT_GE(ret, 0); |
|
EXPECT_TRUE(End_to_End(sync.tagStr, "new=1d 2:03:58")); |
|
} |
|
} |
|
} |
|
|
|
static bool reportedSecurity(const char* command) { |
|
logcat_define(ctx); |
|
FILE* fp = logcat_popen(ctx, command); |
|
if (!fp) return true; |
|
|
|
std::string ret; |
|
bool val = android::base::ReadFdToString(fileno(fp), &ret); |
|
logcat_pclose(ctx, fp); |
|
|
|
if (!val) return true; |
|
return std::string::npos != ret.find("'security'"); |
|
} |
|
|
|
TEST(logcat, security) { |
|
EXPECT_FALSE(reportedSecurity(logcat_executable " -b all -g 2>&1")); |
|
EXPECT_TRUE(reportedSecurity(logcat_executable " -b security -g 2>&1")); |
|
EXPECT_TRUE(reportedSecurity(logcat_executable " -b security -c 2>&1")); |
|
EXPECT_TRUE( |
|
reportedSecurity(logcat_executable " -b security -G 256K 2>&1")); |
|
} |
|
|
|
static size_t commandOutputSize(const char* command) { |
|
logcat_define(ctx); |
|
FILE* fp = logcat_popen(ctx, command); |
|
if (!fp) return 0; |
|
|
|
std::string ret; |
|
if (!android::base::ReadFdToString(fileno(fp), &ret)) return 0; |
|
if (logcat_pclose(ctx, fp) != 0) return 0; |
|
|
|
return ret.size(); |
|
} |
|
|
|
TEST(logcat, help) { |
|
size_t logcatHelpTextSize = commandOutputSize(logcat_executable " -h 2>&1"); |
|
EXPECT_GT(logcatHelpTextSize, 4096UL); |
|
size_t logcatLastHelpTextSize = |
|
commandOutputSize(logcat_executable " -L -h 2>&1"); |
|
#ifdef USING_LOGCAT_EXECUTABLE_DEFAULT // logcat and liblogcat |
|
EXPECT_EQ(logcatHelpTextSize, logcatLastHelpTextSize); |
|
#else |
|
// logcatd -L -h prints the help twice, as designed. |
|
EXPECT_EQ(logcatHelpTextSize * 2, logcatLastHelpTextSize); |
|
#endif |
|
}
|
|
|