Merge branch 'master' of github.com:grpc/grpc into grpclb_api

pull/4038/head
David Garcia Quintas 9 years ago
commit 12fc53f21f
  1. 3
      .gitignore
  2. 19
      build.yaml
  3. 3
      src/boringssl/gen_build_yaml.py
  4. 13
      src/core/transport/chttp2/timeout_encoding.c
  5. 83
      src/python/grpcio/tests/unit/_cython/_channel_test.py
  6. 3
      templates/tools/run_tests/tests.json.template
  7. 13
      test/core/bad_client/gen_build_yaml.py
  8. 11
      test/core/bad_ssl/gen_build_yaml.py
  9. 32
      test/core/end2end/gen_build_yaml.py
  10. 9
      test/core/fling/client.c
  11. 19
      test/core/support/avl_test.c
  12. 7
      test/core/transport/chttp2/timeout_encoding_test.c
  13. 7
      test/cpp/qps/qps_driver.cc
  14. 13
      test/cpp/qps/qps_worker.cc
  15. 4
      test/cpp/qps/qps_worker.h
  16. 5
      test/cpp/qps/worker.cc
  17. 3
      tools/buildgen/build-cleaner.py
  18. 3
      tools/distrib/check_copyright.py
  19. 12
      tools/run_tests/build_artifact_csharp.bat
  20. 5
      tools/run_tests/build_artifacts.py
  21. 39
      tools/run_tests/check_cache_mk.sh
  22. 11
      tools/run_tests/check_submodules.sh
  23. 38
      tools/run_tests/jobset.py
  24. 21
      tools/run_tests/run_tests.py
  25. 10
      tools/run_tests/sanity_tests.yaml
  26. 1222
      tools/run_tests/tests.json

3
.gitignore vendored

@ -82,3 +82,6 @@ DerivedData
# Podfile.lock and the workspace file are tracked, to ease deleting them. That's # Podfile.lock and the workspace file are tracked, to ease deleting them. That's
# needed to trigger "pod install" to rerun the preinstall commands. # needed to trigger "pod install" to rerun the preinstall commands.
Pods/ Pods/
# Artifacts directory
artifacts/

@ -938,6 +938,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: dualstack_socket_test - name: dualstack_socket_test
cpu_cost: 0.1
build: test build: test
language: c language: c
src: src:
@ -1012,6 +1013,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: fling_stream_test - name: fling_stream_test
cpu_cost: 2
build: test build: test
language: c language: c
src: src:
@ -1026,6 +1028,7 @@ targets:
- linux - linux
- posix - posix
- name: fling_test - name: fling_test
cpu_cost: 2
build: test build: test
language: c language: c
src: src:
@ -1134,6 +1137,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: gpr_stack_lockfree_test - name: gpr_stack_lockfree_test
cpu_cost: 10
build: test build: test
language: c language: c
src: src:
@ -1150,6 +1154,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: gpr_sync_test - name: gpr_sync_test
cpu_cost: 10
build: test build: test
language: c language: c
src: src:
@ -1158,6 +1163,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: gpr_thd_test - name: gpr_thd_test
cpu_cost: 10
build: test build: test
language: c language: c
src: src:
@ -1384,6 +1390,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: httpcli_test - name: httpcli_test
cpu_cost: 0.5
build: test build: test
language: c language: c
src: src:
@ -1398,6 +1405,7 @@ targets:
- linux - linux
- posix - posix
- name: httpscli_test - name: httpscli_test
cpu_cost: 0.5
build: test build: test
language: c language: c
src: src:
@ -1479,6 +1487,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: lb_policies_test - name: lb_policies_test
cpu_cost: 0.1
build: test build: test
language: c language: c
src: src:
@ -1531,6 +1540,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: no_server_test - name: no_server_test
cpu_cost: 0.1
build: test build: test
language: c language: c
src: src:
@ -1591,6 +1601,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: set_initial_connect_string_test - name: set_initial_connect_string_test
cpu_cost: 0.1
build: test build: test
language: c language: c
src: src:
@ -1636,6 +1647,7 @@ targets:
- linux - linux
- posix - posix
- name: tcp_client_posix_test - name: tcp_client_posix_test
cpu_cost: 0.5
build: test build: test
language: c language: c
src: src:
@ -1650,6 +1662,7 @@ targets:
- linux - linux
- posix - posix
- name: tcp_posix_test - name: tcp_posix_test
cpu_cost: 0.5
build: test build: test
language: c language: c
src: src:
@ -1879,6 +1892,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: client_crash_test - name: client_crash_test
cpu_cost: 0.1
build: test build: test
language: c++ language: c++
src: src:
@ -1957,6 +1971,7 @@ targets:
- gpr_test_util - gpr_test_util
- gpr - gpr
- name: end2end_test - name: end2end_test
cpu_cost: 0.5
build: test build: test
language: c++ language: c++
src: src:
@ -2110,6 +2125,7 @@ targets:
- linux - linux
- posix - posix
- name: interop_test - name: interop_test
cpu_cost: 0.1
build: test build: test
language: c++ language: c++
src: src:
@ -2201,6 +2217,7 @@ targets:
- linux - linux
- posix - posix
- name: qps_test - name: qps_test
cpu_cost: 10
build: test build: test
language: c++ language: c++
src: src:
@ -2303,6 +2320,7 @@ targets:
- linux - linux
- posix - posix
- name: server_crash_test - name: server_crash_test
cpu_cost: 0.1
build: test build: test
language: c++ language: c++
src: src:
@ -2431,6 +2449,7 @@ targets:
- linux - linux
- posix - posix
- name: thread_stress_test - name: thread_stress_test
cpu_cost: 100
build: test build: test
language: c++ language: c++
src: src:

@ -137,7 +137,8 @@ class Grpc(object):
'platforms': ['linux', 'mac', 'posix', 'windows'], 'platforms': ['linux', 'mac', 'posix', 'windows'],
'flaky': False, 'flaky': False,
'language': 'c++', 'language': 'c++',
'boringssl': True 'boringssl': True,
'cpu_cost': 1.0
} }
for test in files['tests'] for test in files['tests']
] ]

@ -1,6 +1,6 @@
/* /*
* *
* Copyright 2015, Google Inc. * Copyright 2015-2016, Google Inc.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -137,7 +137,7 @@ static int is_all_whitespace(const char *p) {
} }
int grpc_chttp2_decode_timeout(const char *buffer, gpr_timespec *timeout) { int grpc_chttp2_decode_timeout(const char *buffer, gpr_timespec *timeout) {
uint32_t x = 0; int32_t x = 0;
const uint8_t *p = (const uint8_t *)buffer; const uint8_t *p = (const uint8_t *)buffer;
int have_digit = 0; int have_digit = 0;
/* skip whitespace */ /* skip whitespace */
@ -145,13 +145,16 @@ int grpc_chttp2_decode_timeout(const char *buffer, gpr_timespec *timeout) {
; ;
/* decode numeric part */ /* decode numeric part */
for (; *p >= '0' && *p <= '9'; p++) { for (; *p >= '0' && *p <= '9'; p++) {
uint32_t xp = x * 10u + (uint32_t)*p - (uint32_t)'0'; int32_t digit = (int32_t)(*p - (uint8_t)'0');
have_digit = 1; have_digit = 1;
if (xp < x) { /* spec allows max. 8 digits, but we allow values up to 1,000,000,000 */
if (x >= (100 * 1000 * 1000)) {
if (x != (100 * 1000 * 1000) || digit != 0) {
*timeout = gpr_inf_future(GPR_CLOCK_REALTIME); *timeout = gpr_inf_future(GPR_CLOCK_REALTIME);
return 1; return 1;
} }
x = xp; }
x = x * 10 + digit;
} }
if (!have_digit) return 0; if (!have_digit) return 0;
/* skip whitespace */ /* skip whitespace */

@ -0,0 +1,83 @@
# Copyright 2016, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import time
import threading
import unittest
from grpc._cython import cygrpc
# TODO(nathaniel): This should be at least one hundred. Why not one thousand?
_PARALLELISM = 4
def _channel_and_completion_queue():
channel = cygrpc.Channel('localhost:54321', cygrpc.ChannelArgs(()))
completion_queue = cygrpc.CompletionQueue()
return channel, completion_queue
def _connectivity_loop(channel, completion_queue):
for _ in range(100):
connectivity = channel.check_connectivity_state(True)
channel.watch_connectivity_state(
connectivity, cygrpc.Timespec(time.time() + 0.2), completion_queue,
None)
completion_queue.poll(deadline=cygrpc.Timespec(float('+inf')))
def _create_loop_destroy():
channel, completion_queue = _channel_and_completion_queue()
_connectivity_loop(channel, completion_queue)
completion_queue.shutdown()
def _in_parallel(behavior, arguments):
threads = tuple(
threading.Thread(target=behavior, args=arguments)
for _ in range(_PARALLELISM))
for thread in threads:
thread.start()
for thread in threads:
thread.join()
class ChannelTest(unittest.TestCase):
def test_single_channel_lonely_connectivity(self):
channel, completion_queue = _channel_and_completion_queue()
_in_parallel(_connectivity_loop, (channel, completion_queue,))
completion_queue.shutdown()
def test_multiple_channels_lonely_connectivity(self):
_in_parallel(_create_loop_destroy, ())
if __name__ == '__main__':
unittest.main(verbosity=2)

@ -10,7 +10,8 @@
"ci_platforms": tgt.ci_platforms, "ci_platforms": tgt.ci_platforms,
"exclude_configs": tgt.get("exclude_configs", []), "exclude_configs": tgt.get("exclude_configs", []),
"args": [], "args": [],
"flaky": tgt.flaky} "flaky": tgt.flaky,
"cpu_cost": tgt.get("cpu_cost", 1.0)}
for tgt in targets for tgt in targets
if tgt.get('run', True) and tgt.build == 'test'] + if tgt.get('run', True) and tgt.build == 'test'] +
tests, tests,

@ -1,5 +1,5 @@
#!/usr/bin/env python2.7 #!/usr/bin/env python2.7
# Copyright 2015, Google Inc. # Copyright 2015-2016, Google Inc.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -35,15 +35,15 @@
import collections import collections
import yaml import yaml
TestOptions = collections.namedtuple('TestOptions', 'flaky') TestOptions = collections.namedtuple('TestOptions', 'flaky cpu_cost')
default_test_options = TestOptions(False) default_test_options = TestOptions(False, 1.0)
# maps test names to options # maps test names to options
BAD_CLIENT_TESTS = { BAD_CLIENT_TESTS = {
'badreq': default_test_options, 'badreq': default_test_options,
'connection_prefix': default_test_options, 'connection_prefix': default_test_options._replace(cpu_cost=0.2),
'headers': default_test_options, 'headers': default_test_options._replace(cpu_cost=0.2),
'initial_settings_frame': default_test_options, 'initial_settings_frame': default_test_options._replace(cpu_cost=0.2),
'server_registered_method': default_test_options, 'server_registered_method': default_test_options,
'simple_request': default_test_options, 'simple_request': default_test_options,
'window_overflow': default_test_options, 'window_overflow': default_test_options,
@ -75,6 +75,7 @@ def main():
'targets': [ 'targets': [
{ {
'name': '%s_bad_client_test' % t, 'name': '%s_bad_client_test' % t,
'cpu_cost': BAD_CLIENT_TESTS[t].cpu_cost,
'build': 'test', 'build': 'test',
'language': 'c', 'language': 'c',
'secure': 'no', 'secure': 'no',

@ -1,5 +1,5 @@
#!/usr/bin/env python2.7 #!/usr/bin/env python2.7
# Copyright 2015, Google Inc. # Copyright 2015-2016, Google Inc.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -35,13 +35,13 @@
import collections import collections
import yaml import yaml
TestOptions = collections.namedtuple('TestOptions', 'flaky') TestOptions = collections.namedtuple('TestOptions', 'flaky cpu_cost')
default_test_options = TestOptions(False) default_test_options = TestOptions(False, 1.0)
# maps test names to options # maps test names to options
BAD_CLIENT_TESTS = { BAD_CLIENT_TESTS = {
'cert': default_test_options, 'cert': default_test_options._replace(cpu_cost=0.1),
'alpn': default_test_options, 'alpn': default_test_options._replace(cpu_cost=0.1),
} }
def main(): def main():
@ -84,6 +84,7 @@ def main():
for t in sorted(BAD_CLIENT_TESTS.keys())] + [ for t in sorted(BAD_CLIENT_TESTS.keys())] + [
{ {
'name': 'bad_ssl_%s_test' % t, 'name': 'bad_ssl_%s_test' % t,
'cpu_cost': BAD_CLIENT_TESTS[t].cpu_cost,
'build': 'test', 'build': 'test',
'language': 'c', 'language': 'c',
'src': ['test/core/bad_ssl/bad_ssl_test.c'], 'src': ['test/core/bad_ssl/bad_ssl_test.c'],

@ -77,40 +77,42 @@ END2END_FIXTURES = {
} }
TestOptions = collections.namedtuple( TestOptions = collections.namedtuple(
'TestOptions', 'needs_fullstack needs_dns proxyable secure traceable') 'TestOptions', 'needs_fullstack needs_dns proxyable secure traceable cpu_cost')
default_test_options = TestOptions(False, False, True, False, True) default_test_options = TestOptions(False, False, True, False, True, 1.0)
connectivity_test_options = default_test_options._replace(needs_fullstack=True) connectivity_test_options = default_test_options._replace(needs_fullstack=True)
LOWCPU = 0.1
# maps test names to options # maps test names to options
END2END_TESTS = { END2END_TESTS = {
'bad_hostname': default_test_options, 'bad_hostname': default_test_options,
'binary_metadata': default_test_options, 'binary_metadata': default_test_options,
'call_creds': default_test_options._replace(secure=True), 'call_creds': default_test_options._replace(secure=True),
'cancel_after_accept': default_test_options, 'cancel_after_accept': default_test_options._replace(cpu_cost=LOWCPU),
'cancel_after_client_done': default_test_options, 'cancel_after_client_done': default_test_options._replace(cpu_cost=LOWCPU),
'cancel_after_invoke': default_test_options, 'cancel_after_invoke': default_test_options._replace(cpu_cost=LOWCPU),
'cancel_before_invoke': default_test_options, 'cancel_before_invoke': default_test_options._replace(cpu_cost=LOWCPU),
'cancel_in_a_vacuum': default_test_options, 'cancel_in_a_vacuum': default_test_options._replace(cpu_cost=LOWCPU),
'cancel_with_status': default_test_options, 'cancel_with_status': default_test_options._replace(cpu_cost=LOWCPU),
'channel_connectivity': connectivity_test_options._replace(proxyable=False), 'channel_connectivity': connectivity_test_options._replace(proxyable=False, cpu_cost=LOWCPU),
'channel_ping': connectivity_test_options._replace(proxyable=False), 'channel_ping': connectivity_test_options._replace(proxyable=False),
'compressed_payload': default_test_options._replace(proxyable=False), 'compressed_payload': default_test_options._replace(proxyable=False, cpu_cost=LOWCPU),
'default_host': default_test_options._replace(needs_fullstack=True, 'default_host': default_test_options._replace(needs_fullstack=True,
needs_dns=True), needs_dns=True),
'disappearing_server': connectivity_test_options, 'disappearing_server': connectivity_test_options,
'empty_batch': default_test_options, 'empty_batch': default_test_options,
'graceful_server_shutdown': default_test_options, 'graceful_server_shutdown': default_test_options._replace(cpu_cost=LOWCPU),
'hpack_size': default_test_options._replace(proxyable=False, 'hpack_size': default_test_options._replace(proxyable=False,
traceable=False), traceable=False),
'high_initial_seqno': default_test_options, 'high_initial_seqno': default_test_options,
'invoke_large_request': default_test_options, 'invoke_large_request': default_test_options,
'large_metadata': default_test_options, 'large_metadata': default_test_options,
'max_concurrent_streams': default_test_options._replace(proxyable=False), 'max_concurrent_streams': default_test_options._replace(proxyable=False),
'max_message_length': default_test_options, 'max_message_length': default_test_options._replace(cpu_cost=LOWCPU),
'metadata': default_test_options, 'metadata': default_test_options,
'negative_deadline': default_test_options, 'negative_deadline': default_test_options,
'no_op': default_test_options, 'no_op': default_test_options,
'payload': default_test_options, 'payload': default_test_options._replace(cpu_cost=LOWCPU),
'ping_pong_streaming': default_test_options, 'ping_pong_streaming': default_test_options,
'registered_call': default_test_options, 'registered_call': default_test_options,
'request_with_flags': default_test_options._replace(proxyable=False), 'request_with_flags': default_test_options._replace(proxyable=False),
@ -118,7 +120,7 @@ END2END_TESTS = {
'server_finishes_request': default_test_options, 'server_finishes_request': default_test_options,
'shutdown_finishes_calls': default_test_options, 'shutdown_finishes_calls': default_test_options,
'shutdown_finishes_tags': default_test_options, 'shutdown_finishes_tags': default_test_options,
'simple_delayed_request': connectivity_test_options, 'simple_delayed_request': connectivity_test_options._replace(cpu_cost=LOWCPU),
'simple_request': default_test_options, 'simple_request': default_test_options,
'trailing_metadata': default_test_options, 'trailing_metadata': default_test_options,
} }
@ -252,6 +254,7 @@ def main():
END2END_FIXTURES[f].platforms, 'mac')), END2END_FIXTURES[f].platforms, 'mac')),
'flaky': False, 'flaky': False,
'language': 'c', 'language': 'c',
'cpu_cost': END2END_TESTS[t].cpu_cost,
} }
for f in sorted(END2END_FIXTURES.keys()) for f in sorted(END2END_FIXTURES.keys())
for t in sorted(END2END_TESTS.keys()) if compatible(f, t) for t in sorted(END2END_TESTS.keys()) if compatible(f, t)
@ -266,6 +269,7 @@ def main():
END2END_FIXTURES[f].platforms, 'mac')), END2END_FIXTURES[f].platforms, 'mac')),
'flaky': False, 'flaky': False,
'language': 'c', 'language': 'c',
'cpu_cost': END2END_TESTS[t].cpu_cost,
} }
for f in sorted(END2END_FIXTURES.keys()) for f in sorted(END2END_FIXTURES.keys())
if not END2END_FIXTURES[f].secure if not END2END_FIXTURES[f].secure

@ -1,6 +1,6 @@
/* /*
* *
* Copyright 2015, Google Inc. * Copyright 2015-2016, Google Inc.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -201,13 +201,16 @@ int main(int argc, char **argv) {
sc.init(); sc.init();
for (i = 0; i < 1000; i++) { gpr_timespec end_warmup = GRPC_TIMEOUT_SECONDS_TO_DEADLINE(3);
gpr_timespec end_profiling = GRPC_TIMEOUT_SECONDS_TO_DEADLINE(30);
while (gpr_time_cmp(gpr_now(end_warmup.clock_type), end_warmup) < 0) {
sc.do_one_step(); sc.do_one_step();
} }
gpr_log(GPR_INFO, "start profiling"); gpr_log(GPR_INFO, "start profiling");
grpc_profiler_start("client.prof"); grpc_profiler_start("client.prof");
for (i = 0; i < 100000; i++) { while (gpr_time_cmp(gpr_now(end_profiling.clock_type), end_profiling) < 0) {
start = now(); start = now();
sc.do_one_step(); sc.do_one_step();
stop = now(); stop = now();

@ -1,6 +1,6 @@
/* /*
* *
* Copyright 2015, Google Inc. * Copyright 2015-2016, Google Inc.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -3611,32 +3611,33 @@ static void test_badcase3(void) {
gpr_avl_unref(avl); gpr_avl_unref(avl);
} }
static void test_stress(void) { static void test_stress(int amount_of_stress) {
int added[1024]; int added[1024];
int i, j; int i, j;
int deletions = 0; int deletions = 0;
gpr_avl avl; gpr_avl avl;
gpr_log(GPR_DEBUG, "test_stress"); unsigned seed = (unsigned)time(NULL);
gpr_log(GPR_DEBUG, "test_stress amount=%d seed=%u", amount_of_stress, seed);
srand((unsigned)time(NULL)); srand((unsigned)time(NULL));
avl = gpr_avl_create(&int_int_vtable); avl = gpr_avl_create(&int_int_vtable);
memset(added, 0, sizeof(added)); memset(added, 0, sizeof(added));
for (i = 1; deletions < 1000; i++) { for (i = 1; deletions < amount_of_stress; i++) {
int idx = rand() % (int)GPR_ARRAY_SIZE(added); int idx = rand() % (int)GPR_ARRAY_SIZE(added);
GPR_ASSERT(i); GPR_ASSERT(i);
if (rand() < RAND_MAX / 2) { if (rand() < RAND_MAX / 2) {
added[idx] = i; added[idx] = i;
fprintf(stderr, "avl = gpr_avl_add(avl, box(%d), box(%d)); /* d=%d */\n", printf("avl = gpr_avl_add(avl, box(%d), box(%d)); /* d=%d */\n", idx, i,
idx, i, deletions); deletions);
avl = gpr_avl_add(avl, box(idx), box(i)); avl = gpr_avl_add(avl, box(idx), box(i));
} else { } else {
deletions += (added[idx] != 0); deletions += (added[idx] != 0);
added[idx] = 0; added[idx] = 0;
fprintf(stderr, "avl = remove_int(avl, %d); /* d=%d */\n", idx, printf("avl = remove_int(avl, %d); /* d=%d */\n", idx, deletions);
deletions);
avl = remove_int(avl, idx); avl = remove_int(avl, idx);
} }
for (j = 0; j < (int)GPR_ARRAY_SIZE(added); j++) { for (j = 0; j < (int)GPR_ARRAY_SIZE(added); j++) {
@ -3665,7 +3666,7 @@ int main(int argc, char *argv[]) {
test_badcase1(); test_badcase1();
test_badcase2(); test_badcase2();
test_badcase3(); test_badcase3();
test_stress(); test_stress(10);
return 0; return 0;
} }

@ -1,6 +1,6 @@
/* /*
* *
* Copyright 2015, Google Inc. * Copyright 2015-2016, Google Inc.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -126,8 +126,13 @@ void test_decoding(void) {
decode_suite('S', gpr_time_from_seconds); decode_suite('S', gpr_time_from_seconds);
decode_suite('M', gpr_time_from_minutes); decode_suite('M', gpr_time_from_minutes);
decode_suite('H', gpr_time_from_hours); decode_suite('H', gpr_time_from_hours);
assert_decodes_as("1000000000S",
gpr_time_from_seconds(1000 * 1000 * 1000, GPR_TIMESPAN));
assert_decodes_as("1000000000000000000000u", assert_decodes_as("1000000000000000000000u",
gpr_inf_future(GPR_CLOCK_REALTIME)); gpr_inf_future(GPR_CLOCK_REALTIME));
assert_decodes_as("1000000001S", gpr_inf_future(GPR_CLOCK_REALTIME));
assert_decodes_as("2000000001S", gpr_inf_future(GPR_CLOCK_REALTIME));
assert_decodes_as("9999999999S", gpr_inf_future(GPR_CLOCK_REALTIME));
} }
void test_decoding_fails(void) { void test_decoding_fails(void) {

@ -165,6 +165,13 @@ static void QpsDriver() {
server_config.mutable_security_params()->CopyFrom(security); server_config.mutable_security_params()->CopyFrom(security);
} }
// Make sure that if we are performing a generic (bytebuf) test
// that we are also using async streaming
GPR_ASSERT(!client_config.payload_config().has_bytebuf_params() ||
(client_config.client_type() == ASYNC_CLIENT &&
client_config.rpc_type() == STREAMING &&
server_config.server_type() == ASYNC_SERVER));
const auto result = RunScenario( const auto result = RunScenario(
client_config, FLAGS_num_clients, server_config, FLAGS_num_servers, client_config, FLAGS_num_clients, server_config, FLAGS_num_servers,
FLAGS_warmup_seconds, FLAGS_benchmark_seconds, FLAGS_local_workers); FLAGS_warmup_seconds, FLAGS_benchmark_seconds, FLAGS_local_workers);

@ -51,11 +51,11 @@
#include <grpc/support/host_port.h> #include <grpc/support/host_port.h>
#include <grpc/support/log.h> #include <grpc/support/log.h>
#include "src/proto/grpc/testing/services.pb.h"
#include "test/core/util/grpc_profiler.h" #include "test/core/util/grpc_profiler.h"
#include "test/cpp/qps/client.h" #include "test/cpp/qps/client.h"
#include "test/cpp/qps/server.h" #include "test/cpp/qps/server.h"
#include "test/cpp/util/create_test_channel.h" #include "test/cpp/util/create_test_channel.h"
#include "src/proto/grpc/testing/services.pb.h"
namespace grpc { namespace grpc {
namespace testing { namespace testing {
@ -97,7 +97,8 @@ static std::unique_ptr<Server> CreateServer(const ServerConfig& config) {
class WorkerServiceImpl GRPC_FINAL : public WorkerService::Service { class WorkerServiceImpl GRPC_FINAL : public WorkerService::Service {
public: public:
explicit WorkerServiceImpl() : acquired_(false) {} explicit WorkerServiceImpl(int server_port)
: acquired_(false), server_port_(server_port) {}
Status RunClient(ServerContext* ctx, Status RunClient(ServerContext* ctx,
ServerReaderWriter<ClientStatus, ClientArgs>* stream) ServerReaderWriter<ClientStatus, ClientArgs>* stream)
@ -196,6 +197,9 @@ class WorkerServiceImpl GRPC_FINAL : public WorkerService::Service {
if (!args.has_setup()) { if (!args.has_setup()) {
return Status(StatusCode::INVALID_ARGUMENT, ""); return Status(StatusCode::INVALID_ARGUMENT, "");
} }
if (server_port_ != 0) {
args.mutable_setup()->set_port(server_port_);
}
auto server = CreateServer(args.setup()); auto server = CreateServer(args.setup());
if (!server) { if (!server) {
return Status(StatusCode::INVALID_ARGUMENT, ""); return Status(StatusCode::INVALID_ARGUMENT, "");
@ -219,10 +223,11 @@ class WorkerServiceImpl GRPC_FINAL : public WorkerService::Service {
std::mutex mu_; std::mutex mu_;
bool acquired_; bool acquired_;
int server_port_;
}; };
QpsWorker::QpsWorker(int driver_port) { QpsWorker::QpsWorker(int driver_port, int server_port) {
impl_.reset(new WorkerServiceImpl()); impl_.reset(new WorkerServiceImpl(server_port));
char* server_address = NULL; char* server_address = NULL;
gpr_join_host_port(&server_address, "::", driver_port); gpr_join_host_port(&server_address, "::", driver_port);

@ -1,6 +1,6 @@
/* /*
* *
* Copyright 2015, Google Inc. * Copyright 2015-2016, Google Inc.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -46,7 +46,7 @@ class WorkerServiceImpl;
class QpsWorker { class QpsWorker {
public: public:
explicit QpsWorker(int driver_port); explicit QpsWorker(int driver_port, int server_port = 0);
~QpsWorker(); ~QpsWorker();
private: private:

@ -1,6 +1,6 @@
/* /*
* *
* Copyright 2015, Google Inc. * Copyright 2015-2016, Google Inc.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -44,6 +44,7 @@
#include "test/cpp/util/test_config.h" #include "test/cpp/util/test_config.h"
DEFINE_int32(driver_port, 0, "Port for communication with driver"); DEFINE_int32(driver_port, 0, "Port for communication with driver");
DEFINE_int32(server_port, 0, "Port for operation as a server");
static bool got_sigint = false; static bool got_sigint = false;
@ -53,7 +54,7 @@ namespace grpc {
namespace testing { namespace testing {
static void RunServer() { static void RunServer() {
QpsWorker worker(FLAGS_driver_port); QpsWorker worker(FLAGS_driver_port, FLAGS_server_port);
while (!got_sigint) { while (!got_sigint) {
gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),

@ -1,5 +1,5 @@
#!/usr/bin/env python2.7 #!/usr/bin/env python2.7
# Copyright 2015, Google Inc. # Copyright 2015-2016, Google Inc.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -41,6 +41,7 @@ _TOP_LEVEL_KEYS = ['settings', 'proto_deps', 'filegroups', 'libs', 'targets', 'v
_VERSION_KEYS = ['major', 'minor', 'micro', 'build'] _VERSION_KEYS = ['major', 'minor', 'micro', 'build']
_ELEM_KEYS = [ _ELEM_KEYS = [
'name', 'name',
'cpu_cost',
'flaky', 'flaky',
'build', 'build',
'run', 'run',

@ -136,7 +136,10 @@ for filename in subprocess.check_output('git ls-tree -r --name-only -r HEAD',
else: else:
log(args.skips, 'skip', filename) log(args.skips, 'skip', filename)
continue continue
try:
text = load(filename) text = load(filename)
except:
continue
m = re.search(re_license, text) m = re.search(re_license, text)
if m: if m:
gdict = m.groupdict() gdict = m.groupdict()

@ -0,0 +1,12 @@
@rem Builds C# artifacts on Windows
@call vsprojects\build_vs2013.bat %* || goto :error
mkdir artifacts
copy /Y vsprojects\Release\grpc_csharp_ext.dll artifacts || copy /Y vsprojects\x64\Release\grpc_csharp_ext.dll artifacts || goto :error
goto :EOF
:error
echo Failed!
exit /b %errorlevel%

@ -45,7 +45,8 @@ import time
import uuid import uuid
# Docker doesn't clean up after itself, so we do it on exit. # Docker doesn't clean up after itself, so we do it on exit.
atexit.register(lambda: subprocess.call(['stty', 'echo'])) if jobset.platform_string() == 'linux':
atexit.register(lambda: subprocess.call(['stty', 'echo']))
ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..')) ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
os.chdir(ROOT) os.chdir(ROOT)
@ -122,7 +123,7 @@ class CSharpExtArtifact:
if self.platform == 'windows': if self.platform == 'windows':
msbuild_platform = 'Win32' if self.arch == 'x86' else self.arch msbuild_platform = 'Win32' if self.arch == 'x86' else self.arch
return create_jobspec(self.name, return create_jobspec(self.name,
['vsprojects\\build_vs2013.bat', ['tools\\run_tests\\build_artifact_csharp.bat',
'vsprojects\\grpc_csharp_ext.sln', 'vsprojects\\grpc_csharp_ext.sln',
'/p:Configuration=Release', '/p:Configuration=Release',
'/p:PlatformToolset=v120', '/p:PlatformToolset=v120',

@ -0,0 +1,39 @@
#!/bin/sh
# Copyright 2015-2016, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set -e
if [ -f cache.mk ] ; then
echo "Please don't commit cache.mk"
exit 1
fi

@ -44,7 +44,6 @@ cat << EOF | awk '{ print $1 }' | sort > $want_submodules
9f897b25800d2f54f5c442ef01a60721aeca6d87 third_party/boringssl (version_for_cocoapods_1.0-67-g9f897b2) 9f897b25800d2f54f5c442ef01a60721aeca6d87 third_party/boringssl (version_for_cocoapods_1.0-67-g9f897b2)
05b155ff59114735ec8cd089f669c4c3d8f59029 third_party/gflags (v2.1.0-45-g05b155f) 05b155ff59114735ec8cd089f669c4c3d8f59029 third_party/gflags (v2.1.0-45-g05b155f)
c99458533a9b4c743ed51537e25989ea55944908 third_party/googletest (release-1.7.0) c99458533a9b4c743ed51537e25989ea55944908 third_party/googletest (release-1.7.0)
5497a1dfc91a86965383cdd1652e348345400435 third_party/nanopb (nanopb-0.3.3-10-g5497a1d)
8fce8933649ce09c1661ff2b5b7f6eb79badd251 third_party/protobuf (v3.0.0-alpha-4-1-g8fce893) 8fce8933649ce09c1661ff2b5b7f6eb79badd251 third_party/protobuf (v3.0.0-alpha-4-1-g8fce893)
50893291621658f355bc5b4d450a8d06a563053d third_party/zlib (v1.2.8) 50893291621658f355bc5b4d450a8d06a563053d third_party/zlib (v1.2.8)
EOF EOF
@ -53,13 +52,3 @@ diff -u $submodules $want_submodules
rm $submodules $want_submodules rm $submodules $want_submodules
if [ -f cache.mk ] ; then
echo "Please don't commit cache.mk"
exit 1
fi
./tools/buildgen/generate_projects.sh
./tools/distrib/check_copyright.py
./tools/distrib/clang_format_code.sh
./tools/distrib/check_nanopb_output.sh
./tools/distrib/check_trailing_newlines.sh

@ -33,6 +33,7 @@ import hashlib
import multiprocessing import multiprocessing
import os import os
import platform import platform
import re
import signal import signal
import subprocess import subprocess
import sys import sys
@ -40,6 +41,10 @@ import tempfile
import time import time
# cpu cost measurement
measure_cpu_costs = False
_DEFAULT_MAX_JOBS = 16 * multiprocessing.cpu_count() _DEFAULT_MAX_JOBS = 16 * multiprocessing.cpu_count()
_MAX_RESULT_SIZE = 8192 _MAX_RESULT_SIZE = 8192
@ -146,7 +151,7 @@ class JobSpec(object):
def __init__(self, cmdline, shortname=None, environ=None, hash_targets=None, def __init__(self, cmdline, shortname=None, environ=None, hash_targets=None,
cwd=None, shell=False, timeout_seconds=5*60, flake_retries=0, cwd=None, shell=False, timeout_seconds=5*60, flake_retries=0,
timeout_retries=0, kill_handler=None): timeout_retries=0, kill_handler=None, cpu_cost=1.0):
""" """
Arguments: Arguments:
cmdline: a list of arguments to pass as the command line cmdline: a list of arguments to pass as the command line
@ -154,6 +159,7 @@ class JobSpec(object):
hash_targets: which files to include in the hash representing the jobs version hash_targets: which files to include in the hash representing the jobs version
(or empty, indicating the job should not be hashed) (or empty, indicating the job should not be hashed)
kill_handler: a handler that will be called whenever job.kill() is invoked kill_handler: a handler that will be called whenever job.kill() is invoked
cpu_cost: number of cores per second this job needs
""" """
if environ is None: if environ is None:
environ = {} environ = {}
@ -169,6 +175,7 @@ class JobSpec(object):
self.flake_retries = flake_retries self.flake_retries = flake_retries
self.timeout_retries = timeout_retries self.timeout_retries = timeout_retries
self.kill_handler = kill_handler self.kill_handler = kill_handler
self.cpu_cost = cpu_cost
def identity(self): def identity(self):
return '%r %r %r' % (self.cmdline, self.environ, self.hash_targets) return '%r %r %r' % (self.cmdline, self.environ, self.hash_targets)
@ -218,7 +225,10 @@ class Job(object):
env.update(self._spec.environ) env.update(self._spec.environ)
env.update(self._add_env) env.update(self._add_env)
self._start = time.time() self._start = time.time()
try_start = lambda: subprocess.Popen(args=self._spec.cmdline, cmdline = self._spec.cmdline
if measure_cpu_costs:
cmdline = ['time', '--portability'] + cmdline
try_start = lambda: subprocess.Popen(args=cmdline,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
stdout=self._tempfile, stdout=self._tempfile,
cwd=self._spec.cwd, cwd=self._spec.cwd,
@ -267,8 +277,17 @@ class Job(object):
self.result.returncode = self._process.returncode self.result.returncode = self._process.returncode
else: else:
self._state = _SUCCESS self._state = _SUCCESS
message('PASSED', '%s [time=%.1fsec; retries=%d;%d]' % ( measurement = ''
self._spec.shortname, elapsed, self._retries, self._timeout_retries), if measure_cpu_costs:
m = re.search(r'real ([0-9.]+)\nuser ([0-9.]+)\nsys ([0-9.]+)', stdout())
real = float(m.group(1))
user = float(m.group(2))
sys = float(m.group(3))
if real > 0.5:
cores = (user + sys) / real
measurement = '; cpu_cost=%.01f; estimated=%.01f' % (cores, self._spec.cpu_cost)
message('PASSED', '%s [time=%.1fsec; retries=%d:%d%s]' % (
self._spec.shortname, elapsed, self._retries, self._timeout_retries, measurement),
do_newline=self._newline_on_success or self._travis) do_newline=self._newline_on_success or self._travis)
self.result.state = 'PASSED' self.result.state = 'PASSED'
if self._bin_hash: if self._bin_hash:
@ -329,10 +348,19 @@ class Jobset(object):
def get_num_failures(self): def get_num_failures(self):
return self._failures return self._failures
def cpu_cost(self):
c = 0
for job in self._running:
c += job._spec.cpu_cost
return c
def start(self, spec): def start(self, spec):
"""Start a job. Return True on success, False on failure.""" """Start a job. Return True on success, False on failure."""
while len(self._running) >= self._maxjobs: while True:
if self.cancelled(): return False if self.cancelled(): return False
current_cpu_cost = self.cpu_cost()
if current_cpu_cost == 0: break
if current_cpu_cost + spec.cpu_cost < self._maxjobs: break
self.reap() self.reap()
if self.cancelled(): return False if self.cancelled(): return False
if spec.hash_targets: if spec.hash_targets:

@ -78,7 +78,7 @@ class SimpleConfig(object):
self.timeout_multiplier = timeout_multiplier self.timeout_multiplier = timeout_multiplier
def job_spec(self, cmdline, hash_targets, timeout_seconds=5*60, def job_spec(self, cmdline, hash_targets, timeout_seconds=5*60,
shortname=None, environ={}): shortname=None, environ={}, cpu_cost=1.0):
"""Construct a jobset.JobSpec for a test under this config """Construct a jobset.JobSpec for a test under this config
Args: Args:
@ -96,7 +96,8 @@ class SimpleConfig(object):
return jobset.JobSpec(cmdline=cmdline, return jobset.JobSpec(cmdline=cmdline,
shortname=shortname, shortname=shortname,
environ=actual_environ, environ=actual_environ,
timeout_seconds=self.timeout_multiplier * timeout_seconds, cpu_cost=cpu_cost,
timeout_seconds=(self.timeout_multiplier * timeout_seconds if timeout_seconds else None),
hash_targets=hash_targets hash_targets=hash_targets
if self.allow_hashing else None, if self.allow_hashing else None,
flake_retries=5 if args.allow_flakes else 0, flake_retries=5 if args.allow_flakes else 0,
@ -114,11 +115,12 @@ class ValgrindConfig(object):
self.args = args self.args = args
self.allow_hashing = False self.allow_hashing = False
def job_spec(self, cmdline, hash_targets): def job_spec(self, cmdline, hash_targets, cpu_cost=1.0):
return jobset.JobSpec(cmdline=['valgrind', '--tool=%s' % self.tool] + return jobset.JobSpec(cmdline=['valgrind', '--tool=%s' % self.tool] +
self.args + cmdline, self.args + cmdline,
shortname='valgrind %s' % cmdline[0], shortname='valgrind %s' % cmdline[0],
hash_targets=None, hash_targets=None,
cpu_cost=cpu_cost,
flake_retries=5 if args.allow_flakes else 0, flake_retries=5 if args.allow_flakes else 0,
timeout_retries=3 if args.allow_flakes else 0) timeout_retries=3 if args.allow_flakes else 0)
@ -157,6 +159,7 @@ class CLanguage(object):
cmdline = [binary] + target['args'] cmdline = [binary] + target['args']
out.append(config.job_spec(cmdline, [binary], out.append(config.job_spec(cmdline, [binary],
shortname=' '.join(cmdline), shortname=' '.join(cmdline),
cpu_cost=target['cpu_cost'],
environ={'GRPC_DEFAULT_SSL_ROOTS_FILE_PATH': environ={'GRPC_DEFAULT_SSL_ROOTS_FILE_PATH':
os.path.abspath(os.path.dirname( os.path.abspath(os.path.dirname(
sys.argv[0]) + '/../../src/core/tsi/test_creds/ca.pem')})) sys.argv[0]) + '/../../src/core/tsi/test_creds/ca.pem')}))
@ -441,8 +444,10 @@ class ObjCLanguage(object):
class Sanity(object): class Sanity(object):
def test_specs(self, config, args): def test_specs(self, config, args):
return [config.job_spec(['tools/run_tests/run_sanity.sh'], None, timeout_seconds=15*60), import yaml
config.job_spec(['tools/run_tests/check_sources_and_headers.py'], None)] with open('tools/run_tests/sanity_tests.yaml', 'r') as f:
return [config.job_spec([cmd['script']], None, timeout_seconds=None, environ={'TEST': 'true'}, cpu_cost=cmd.get('cpu_cost', 1))
for cmd in yaml.load(f)]
def pre_build_steps(self): def pre_build_steps(self):
return [] return []
@ -600,7 +605,7 @@ argp.add_argument('-n', '--runs_per_test', default=1, type=runs_per_test_type,
help='A positive integer or "inf". If "inf", all tests will run in an ' help='A positive integer or "inf". If "inf", all tests will run in an '
'infinite loop. Especially useful in combination with "-f"') 'infinite loop. Especially useful in combination with "-f"')
argp.add_argument('-r', '--regex', default='.*', type=str) argp.add_argument('-r', '--regex', default='.*', type=str)
argp.add_argument('-j', '--jobs', default=2 * multiprocessing.cpu_count(), type=int) argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
argp.add_argument('-s', '--slowdown', default=1.0, type=float) argp.add_argument('-s', '--slowdown', default=1.0, type=float)
argp.add_argument('-f', '--forever', argp.add_argument('-f', '--forever',
default=False, default=False,
@ -647,6 +652,8 @@ argp.add_argument('--build_only',
action='store_const', action='store_const',
const=True, const=True,
help='Perform all the build steps but dont run any tests.') help='Perform all the build steps but dont run any tests.')
argp.add_argument('--measure_cpu_costs', default=False, action='store_const', const=True,
help='Measure the cpu costs of tests')
argp.add_argument('--update_submodules', default=[], nargs='*', argp.add_argument('--update_submodules', default=[], nargs='*',
help='Update some submodules before building. If any are updated, also run generate_projects. ' + help='Update some submodules before building. If any are updated, also run generate_projects. ' +
'Submodules are specified as SUBMODULE_NAME:BRANCH; if BRANCH is omitted, master is assumed.') 'Submodules are specified as SUBMODULE_NAME:BRANCH; if BRANCH is omitted, master is assumed.')
@ -655,6 +662,8 @@ argp.add_argument('-x', '--xml_report', default=None, type=str,
help='Generates a JUnit-compatible XML report') help='Generates a JUnit-compatible XML report')
args = argp.parse_args() args = argp.parse_args()
jobset.measure_cpu_costs = args.measure_cpu_costs
if args.use_docker: if args.use_docker:
if not args.travis: if not args.travis:
print 'Seen --use_docker flag, will run tests under docker.' print 'Seen --use_docker flag, will run tests under docker.'

@ -0,0 +1,10 @@
# a set of tests that are run in parallel for sanity tests
- script: tools/run_tests/check_sources_and_headers.py
- script: tools/run_tests/check_submodules.sh
- script: tools/run_tests/check_cache_mk.sh
- script: tools/buildgen/generate_projects.sh
cpu_cost: 100
- script: tools/distrib/check_copyright.py
- script: tools/distrib/clang_format_code.sh
- script: tools/distrib/check_trailing_newlines.sh
- script: tools/distrib/check_nanopb_output.sh

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save