diff options
40 files changed, 554 insertions, 664 deletions
diff --git a/fnet/src/tests/examples/examples_test.cpp b/fnet/src/tests/examples/examples_test.cpp index 5debd969a60..b3a21a7bc42 100644 --- a/fnet/src/tests/examples/examples_test.cpp +++ b/fnet/src/tests/examples/examples_test.cpp @@ -3,6 +3,7 @@ #include <vespa/vespalib/util/slaveproc.h> #include <vespa/vespalib/util/stringfmt.h> #include <vespa/vespalib/util/thread.h> +#include <atomic> // reserved in vespa/factory/doc/port-ranges.txt static const int PORT0 = 18570; @@ -18,7 +19,7 @@ static const int PORT9 = 18579; using vespalib::SlaveProc; -bool runProc(SlaveProc &proc, bool &done) { +bool runProc(SlaveProc &proc, std::atomic<bool> &done) { char buf[4096]; proc.close(); // close stdin while (proc.running() && !done) { @@ -43,7 +44,7 @@ bool runProc(const std::string &cmd) { fprintf(stderr, "retrying command in 500ms...\n"); vespalib::Thread::sleep(500); } - bool done = false; + std::atomic<bool> done(false); SlaveProc proc(cmd.c_str()); ok = runProc(proc, done); } @@ -51,193 +52,193 @@ bool runProc(const std::string &cmd) { } TEST("usage") { - bool done = false; + std::atomic<bool> done(false); { - SlaveProc proc("../../examples/proxy/fnet_proxy_app"); + SlaveProc proc("exec ../../examples/proxy/fnet_proxy_app"); EXPECT_FALSE(runProc(proc, done)); } { - SlaveProc proc("../../examples/ping/fnet_pingserver_app"); + SlaveProc proc("exec ../../examples/ping/fnet_pingserver_app"); EXPECT_FALSE(runProc(proc, done)); } { - SlaveProc proc("../../examples/ping/fnet_pingclient_app"); + SlaveProc proc("exec ../../examples/ping/fnet_pingclient_app"); EXPECT_FALSE(runProc(proc, done)); } { - SlaveProc proc("../../examples/frt/rpc/fnet_rpc_client_app"); + SlaveProc proc("exec ../../examples/frt/rpc/fnet_rpc_client_app"); EXPECT_FALSE(runProc(proc, done)); } { - SlaveProc proc("../../examples/frt/rpc/fnet_rpc_server_app"); + SlaveProc proc("exec ../../examples/frt/rpc/fnet_rpc_server_app"); EXPECT_FALSE(runProc(proc, done)); } { - SlaveProc proc("../../examples/frt/rpc/fnet_echo_client_app"); + SlaveProc proc("exec ../../examples/frt/rpc/fnet_echo_client_app"); EXPECT_FALSE(runProc(proc, done)); } { - SlaveProc proc("../../examples/frt/rpc/rpc_info"); + SlaveProc proc("exec ../../examples/frt/rpc/rpc_info"); EXPECT_FALSE(runProc(proc, done)); } { - SlaveProc proc("../../examples/frt/rpc/rpc_invoke"); + SlaveProc proc("exec ../../examples/frt/rpc/rpc_invoke"); EXPECT_FALSE(runProc(proc, done)); } { - SlaveProc proc("../../examples/frt/rpc/fnet_rpc_callback_server_app"); + SlaveProc proc("exec ../../examples/frt/rpc/fnet_rpc_callback_server_app"); EXPECT_FALSE(runProc(proc, done)); } { - SlaveProc proc("../../examples/frt/rpc/fnet_rpc_callback_client_app"); + SlaveProc proc("exec ../../examples/frt/rpc/fnet_rpc_callback_client_app"); EXPECT_FALSE(runProc(proc, done)); } { - SlaveProc proc("../../examples/frt/rpc/rpc_proxy"); + SlaveProc proc("exec ../../examples/frt/rpc/rpc_proxy"); EXPECT_FALSE(runProc(proc, done)); } } TEST("timeout") { std::string out; - EXPECT_TRUE(SlaveProc::run("../../examples/timeout/fnet_timeout_app", out)); + EXPECT_TRUE(SlaveProc::run("exec ../../examples/timeout/fnet_timeout_app", out)); fprintf(stderr, "%s\n", out.c_str()); } -TEST_MT_F("ping", 2, bool()) { +TEST_MT_F("ping", 2, std::atomic<bool>()) { if (thread_id == 0) { - SlaveProc proc(vespalib::make_string("../../examples/ping/fnet_pingserver_app tcp/%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/ping/fnet_pingserver_app tcp/%d", PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else { TEST_BARRIER(); - EXPECT_TRUE(runProc(vespalib::make_string("../../examples/ping/fnet_pingclient_app tcp/localhost:%d", + EXPECT_TRUE(runProc(vespalib::make_string("exec ../../examples/ping/fnet_pingclient_app tcp/localhost:%d", PORT0).c_str())); f1 = true; } } -TEST_MT_F("ping times out", 2, bool()) { +TEST_MT_F("ping times out", 2, std::atomic<bool>()) { if (thread_id == 0) { - SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d", PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else { TEST_BARRIER(); - EXPECT_TRUE(runProc(vespalib::make_string("../../examples/ping/fnet_pingclient_app tcp/localhost:%d", + EXPECT_TRUE(runProc(vespalib::make_string("exec ../../examples/ping/fnet_pingclient_app tcp/localhost:%d", PORT0).c_str())); f1 = true; } } -TEST_MT_F("ping with proxy", 3, bool()) { +TEST_MT_F("ping with proxy", 3, std::atomic<bool>()) { if (thread_id == 0) { - SlaveProc proc(vespalib::make_string("../../examples/ping/fnet_pingserver_app tcp/%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/ping/fnet_pingserver_app tcp/%d", PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else if (thread_id == 1) { - SlaveProc proc(vespalib::make_string("../../examples/proxy/fnet_proxy_app tcp/%d tcp/localhost:%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/proxy/fnet_proxy_app tcp/%d tcp/localhost:%d", PORT1, PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else { TEST_BARRIER(); - EXPECT_TRUE(runProc(vespalib::make_string("../../examples/ping/fnet_pingclient_app tcp/localhost:%d", + EXPECT_TRUE(runProc(vespalib::make_string("exec ../../examples/ping/fnet_pingclient_app tcp/localhost:%d", PORT1).c_str())); f1 = true; } } -TEST_MT_F("rpc client server", 2, bool()) { +TEST_MT_F("rpc client server", 2, std::atomic<bool>()) { if (thread_id == 0) { - SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d", PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else { TEST_BARRIER(); - EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_client_app tcp/localhost:%d", + EXPECT_TRUE(runProc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_client_app tcp/localhost:%d", PORT0).c_str())); f1 = true; } } -TEST_MT_F("rpc echo client", 2, bool()) { +TEST_MT_F("rpc echo client", 2, std::atomic<bool>()) { if (thread_id == 0) { - SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d", PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else { TEST_BARRIER(); - EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_echo_client_app tcp/localhost:%d", + EXPECT_TRUE(runProc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_echo_client_app tcp/localhost:%d", PORT0).c_str())); f1 = true; } } -TEST_MT_F("rpc info", 2, bool()) { +TEST_MT_F("rpc info", 2, std::atomic<bool>()) { if (thread_id == 0) { - SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d", PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else { TEST_BARRIER(); - EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/rpc_info tcp/localhost:%d", + EXPECT_TRUE(runProc(vespalib::make_string("exec ../../examples/frt/rpc/rpc_info tcp/localhost:%d", PORT0).c_str())); - EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/rpc_info tcp/localhost:%d verbose", + EXPECT_TRUE(runProc(vespalib::make_string("exec ../../examples/frt/rpc/rpc_info tcp/localhost:%d verbose", PORT0).c_str())); f1 = true; } } -TEST_MT_F("rpc invoke", 2, bool()) { +TEST_MT_F("rpc invoke", 2, std::atomic<bool>()) { if (thread_id == 0) { - SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_server_app tcp/%d", PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else { TEST_BARRIER(); - EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/rpc_invoke tcp/localhost:%d frt.rpc.echo " + EXPECT_TRUE(runProc(vespalib::make_string("exec ../../examples/frt/rpc/rpc_invoke tcp/localhost:%d frt.rpc.echo " "b:1 h:2 i:4 l:8 f:0.5 d:0.25 s:foo", PORT0).c_str())); f1 = true; } } -TEST_MT_F("rpc callback client server", 2, bool()) { +TEST_MT_F("rpc callback client server", 2, std::atomic<bool>()) { if (thread_id == 0) { - SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d", PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else { TEST_BARRIER(); - EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_client_app tcp/localhost:%d", + EXPECT_TRUE(runProc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_callback_client_app tcp/localhost:%d", PORT0).c_str())); f1 = true; } } -TEST_MT_F("rpc callback client server with proxy", 3, bool()) { +TEST_MT_F("rpc callback client server with proxy", 3, std::atomic<bool>()) { if (thread_id == 0) { - SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d", PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else if (thread_id == 1) { - SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/rpc_proxy tcp/%d tcp/localhost:%d", + SlaveProc proc(vespalib::make_string("exec ../../examples/frt/rpc/rpc_proxy tcp/%d tcp/localhost:%d", PORT1, PORT0).c_str()); TEST_BARRIER(); EXPECT_TRUE(runProc(proc, f1)); } else { TEST_BARRIER(); - EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_client_app tcp/localhost:%d", + EXPECT_TRUE(runProc(vespalib::make_string("exec ../../examples/frt/rpc/fnet_rpc_callback_client_app tcp/localhost:%d", PORT1).c_str())); f1 = true; } diff --git a/fsa/src/alltest/alltest.sh b/fsa/src/alltest/alltest.sh index ad9994b7464..37274721e25 100755 --- a/fsa/src/alltest/alltest.sh +++ b/fsa/src/alltest/alltest.sh @@ -1,5 +1,4 @@ #!/bin/bash -set -e ./detector_test.sh ./fsa_test.sh ./fsa_fsa_create_test_app diff --git a/lowercasing_test/CMakeLists.txt b/lowercasing_test/CMakeLists.txt index 159b6f6a4d4..79da02985b3 100644 --- a/lowercasing_test/CMakeLists.txt +++ b/lowercasing_test/CMakeLists.txt @@ -8,11 +8,7 @@ vespa_define_module( APPS src/binref - src/java TESTS - src/grouping_test/hello-world - src/grouping_test/hello-world-lib - src/tests/hello-world src/tests/lowercasing ) diff --git a/lowercasing_test/README b/lowercasing_test/README index 347914fce0c..883d38fffff 100644 --- a/lowercasing_test/README +++ b/lowercasing_test/README @@ -1,5 +1 @@ This module was created to test that lowercasing is consistent across Java and C++. - -If you want to copy the infrastructure from this module you should try -to copy the hello-world test; it is designed to verify the integrity -of the testing module itself. diff --git a/lowercasing_test/src/.gitignore b/lowercasing_test/src/.gitignore index a39df0815b3..d4590f3a58c 100644 --- a/lowercasing_test/src/.gitignore +++ b/lowercasing_test/src/.gitignore @@ -1,3 +1,2 @@ Makefile.ini config_command.sh -project.dsw diff --git a/lowercasing_test/src/binref/env.sh.in b/lowercasing_test/src/binref/env.sh.in index 6a0770c5f9b..dda4234226f 100644 --- a/lowercasing_test/src/binref/env.sh.in +++ b/lowercasing_test/src/binref/env.sh.in @@ -1,4 +1,2 @@ BINREF=@CMAKE_CURRENT_BINARY_DIR@ export BINREF -HELLO_WORLD_APP=$BINREF/../grouping_test/hello-world/lowercasing_test_hello-world_app -export HELLO_WORLD_APP diff --git a/lowercasing_test/src/binref/progctl.sh b/lowercasing_test/src/binref/progctl.sh deleted file mode 120000 index 2c1fb1d47ce..00000000000 --- a/lowercasing_test/src/binref/progctl.sh +++ /dev/null @@ -1 +0,0 @@ -../../../vespalib/src/vespa/vespalib/testkit/progctl.sh
\ No newline at end of file diff --git a/lowercasing_test/src/grouping_test/hello-world-lib/.gitignore b/lowercasing_test/src/grouping_test/hello-world-lib/.gitignore deleted file mode 100644 index 5dae353d999..00000000000 --- a/lowercasing_test/src/grouping_test/hello-world-lib/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.depend -Makefile diff --git a/lowercasing_test/src/grouping_test/hello-world-lib/CMakeLists.txt b/lowercasing_test/src/grouping_test/hello-world-lib/CMakeLists.txt deleted file mode 100644 index a6e6876f152..00000000000 --- a/lowercasing_test/src/grouping_test/hello-world-lib/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_library(lowercasing_test_hello-world-lib STATIC - SOURCES - hello-world.cpp - DEPENDS -) diff --git a/lowercasing_test/src/grouping_test/hello-world-lib/hello-world.cpp b/lowercasing_test/src/grouping_test/hello-world-lib/hello-world.cpp deleted file mode 100644 index d280665ae7d..00000000000 --- a/lowercasing_test/src/grouping_test/hello-world-lib/hello-world.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include "hello-world.h" -#include <stdio.h> - -void -HelloWorld::print() -{ - fprintf(stdout, "C++/lib/Hello World\n"); -} diff --git a/lowercasing_test/src/grouping_test/hello-world-lib/hello-world.h b/lowercasing_test/src/grouping_test/hello-world-lib/hello-world.h deleted file mode 100644 index 4bca4064da0..00000000000 --- a/lowercasing_test/src/grouping_test/hello-world-lib/hello-world.h +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#pragma once - -class HelloWorld -{ -public: - static void print(); -}; - diff --git a/lowercasing_test/src/grouping_test/hello-world/.gitignore b/lowercasing_test/src/grouping_test/hello-world/.gitignore deleted file mode 100644 index 49082b83c4d..00000000000 --- a/lowercasing_test/src/grouping_test/hello-world/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.depend -Makefile -hello-world -lowercasing_test_hello-world_app diff --git a/lowercasing_test/src/grouping_test/hello-world/CMakeLists.txt b/lowercasing_test/src/grouping_test/hello-world/CMakeLists.txt deleted file mode 100644 index 2d6940ef4d8..00000000000 --- a/lowercasing_test/src/grouping_test/hello-world/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_executable(lowercasing_test_hello-world_app - SOURCES - hello-world.cpp - INSTALL bin - DEPENDS - lowercasing_test_hello-world-lib -) diff --git a/lowercasing_test/src/grouping_test/hello-world/hello-world.cpp b/lowercasing_test/src/grouping_test/hello-world/hello-world.cpp deleted file mode 100644 index 42504323bbe..00000000000 --- a/lowercasing_test/src/grouping_test/hello-world/hello-world.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include <vespa/fastos/fastos.h> -#include <grouping_test/hello-world-lib/hello-world.h> - -class App : public FastOS_Application -{ -public: - int Main(); -}; - -int -App::Main() -{ - HelloWorld::print(); - fprintf(stdout, "C++/app/Hello World\n"); - return 0; -} - -int -main(int argc, char **argv) -{ - App myapp; - return myapp.Entry(argc, argv); -} diff --git a/lowercasing_test/src/java/.gitignore b/lowercasing_test/src/java/.gitignore deleted file mode 100644 index b9e1611ac9e..00000000000 --- a/lowercasing_test/src/java/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -build.inc -classes -lowercasing_test.jar -java_code_compiled diff --git a/lowercasing_test/src/java/CMakeLists.txt b/lowercasing_test/src/java/CMakeLists.txt deleted file mode 100644 index 34b531c2093..00000000000 --- a/lowercasing_test/src/java/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/java_code_compiled - COMMAND ant -q -buildfile ${CMAKE_CURRENT_SOURCE_DIR}/build.xml >>/dev/null - COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/java_code_compiled - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/build.xml ${CMAKE_CURRENT_SOURCE_DIR}/HelloWorld.java) -add_custom_target(lowercasing_test_java ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/java_code_compiled) diff --git a/lowercasing_test/src/java/HelloWorld.java b/lowercasing_test/src/java/HelloWorld.java deleted file mode 100644 index 37d7bf11af7..00000000000 --- a/lowercasing_test/src/java/HelloWorld.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -public class HelloWorld -{ - public static void main(String[] args) - { - System.out.println("Java/jar/HelloWorld"); - } -} diff --git a/lowercasing_test/src/java/build.xml b/lowercasing_test/src/java/build.xml deleted file mode 100644 index 9e70af511f4..00000000000 --- a/lowercasing_test/src/java/build.xml +++ /dev/null @@ -1,61 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> -<project basedir="." default="all" name="lowercasing_test"> - <!-- Written to assume that classpath is rooted in the current directory. --> - <!-- So this should be OK if you make this script in the root of a filesystem. --> - <!-- If not, just change src.dir to be the root of your sources' package tree --> - <!-- and use e.g. View over a Filesystem to mount that subdirectory with all capabilities. --> - <!-- The idea is that both Ant and NetBeans have to know what the package root is --> - <!-- for the classes in your application. --> - - <!-- Don't worry if you don't know the Ant syntax completely or need help on some tasks! --> - <!-- The standard Ant documentation can be downloaded from AutoUpdate and --> - <!-- and then you can access the Ant manual in the online help. --> - - <property file="build.inc" /> - - <target name="init"> - <property location="classes" name="classes.dir"/> - <property location="." name="src.dir"/> - <property location="doc/api" name="javadoc.dir"/> - <property name="project.name" value="${ant.project.name}"/> - <property location="${project.name}.jar" name="jar"/> - </target> - - <target depends="init" name="compile"> - <!-- Both srcdir and destdir should be package roots. --> - <mkdir dir="${classes.dir}"/> - <javac debug="true" deprecation="true" destdir="${classes.dir}" srcdir="${src.dir}" includeantruntime="false"> - <classpath> - </classpath> - <!-- To add something to the classpath: --> - <!-- <classpath><pathelement location="${mylib}"/></classpath> --> - <!-- To exclude some files: --> - <!-- <exclude name="com/foo/SomeFile.java"/><exclude name="com/foo/somepackage/"/> --> - </javac> - </target> - - <target depends="init,compile" name="jar"> - <!-- To make a standalone app, insert into <jar>: --> - <!-- <manifest><attribute name="Main-Class" value="com.foo.Main"/></manifest> --> - <jar basedir="${classes.dir}" compress="true" jarfile="${jar}"/> - </target> - - <target depends="init,jar" description="Build everything." name="all"/> - - <target depends="init" description="Javadoc for my API." name="javadoc"> - <mkdir dir="${javadoc.dir}"/> - <javadoc destdir="${javadoc.dir}" packagenames="*"> - <sourcepath> - <pathelement location="${src.dir}"/> - </sourcepath> - </javadoc> - </target> - - <target depends="init" description="Clean all build products." name="clean"> - <delete dir="${classes.dir}"/> - <delete dir="${javadoc.dir}"/> - <delete file="${jar}"/> - </target> - -</project> diff --git a/lowercasing_test/src/testlist.txt b/lowercasing_test/src/testlist.txt deleted file mode 100644 index e5735a07456..00000000000 --- a/lowercasing_test/src/testlist.txt +++ /dev/null @@ -1,2 +0,0 @@ -tests/hello-world -tests/lowercasing diff --git a/lowercasing_test/src/tests/create-test.sh b/lowercasing_test/src/tests/create-test.sh deleted file mode 100755 index feca94c7de2..00000000000 --- a/lowercasing_test/src/tests/create-test.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/sh -# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -gen_ignore_file() { - echo "generating '$1' ..." - echo ".depend" > $1 - echo "Makefile" >> $1 - echo "${test}_test" >> $1 -} - -gen_project_file() { - echo "generating '$1' ..." - echo "APPLICATION ${test}_test" > $1 - echo "OBJS $test" >> $1 - echo "EXTERNALLIBS searchlib" >> $1 - echo "EXTERNALLIBS vespalib" >> $1 - echo "EXTERNALLIBS vespalog" >> $1 - echo "" >> $1 - echo "CUSTOMMAKE" >> $1 - echo "test: depend ${test}_test" >> $1 - echo -e "\t@./${test}_test" >> $1 -} - -gen_source() { - echo "generating '$1' ..." - echo "#include <vespa/log/log.h>" > $1 - echo "LOG_SETUP(\"${test}_test\");" >> $1 - echo "#include <vespa/fastos/fastos.h>" >> $1 - echo "#include <vespa/vespalib/testkit/testapp.h>" >> $1 - echo "" >> $1 - echo "" >> $1 - echo "TEST_SETUP(Test);" >> $1 - echo "" >> $1 - echo "int" >> $1 - echo "Test::Main()" >> $1 - echo "{" >> $1 - echo " TEST_INIT(\"${test}_test\");" >> $1 - echo " TEST_DONE();" >> $1 - echo "}" >> $1 -} - -gen_desc() { - echo "generating '$1' ..." - echo "$test test. Take a look at $test.cpp for details." > $1 -} - -gen_file_list() { - echo "generating '$1' ..." - echo "$test.cpp" > $1 -} - -if [ $# -ne 1 ]; then - echo "usage: $0 <name>" - echo " name: name of the test to create" - exit 1 -fi - -test=$1 -if [ -e $test ]; then - echo "$test already present, don't want to mess it up..." - exit 1 -fi - -echo "creating directory '$test' ..." -mkdir -p $test || exit 1 -cd $test || exit 1 -test=`basename $test` - -gen_ignore_file .cvsignore -gen_project_file fastos.project -gen_source $test.cpp -gen_desc DESC -gen_file_list FILES diff --git a/lowercasing_test/src/tests/hello-world/.gitignore b/lowercasing_test/src/tests/hello-world/.gitignore deleted file mode 100644 index 40a57ff7d39..00000000000 --- a/lowercasing_test/src/tests/hello-world/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.depend -HelloWorldLocal.class -Makefile -hello-world-local -out.txt -lowercasing_test_hello-world-local_app diff --git a/lowercasing_test/src/tests/hello-world/CMakeLists.txt b/lowercasing_test/src/tests/hello-world/CMakeLists.txt deleted file mode 100644 index 079fbe85d71..00000000000 --- a/lowercasing_test/src/tests/hello-world/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_executable(lowercasing_test_hello-world-local_app TEST - SOURCES - hello-world-local.cpp - DEPENDS - lowercasing_test_hello-world-lib -) -vespa_add_test(NAME lowercasing_test_hello-world-local_app NO_VALGRIND COMMAND sh hello-world_test.sh) diff --git a/lowercasing_test/src/tests/hello-world/DESC b/lowercasing_test/src/tests/hello-world/DESC deleted file mode 100644 index 47ab72663ef..00000000000 --- a/lowercasing_test/src/tests/hello-world/DESC +++ /dev/null @@ -1 +0,0 @@ -Initial test to verify the integrity of this testing module. diff --git a/lowercasing_test/src/tests/hello-world/FILES b/lowercasing_test/src/tests/hello-world/FILES deleted file mode 100644 index beec05c0632..00000000000 --- a/lowercasing_test/src/tests/hello-world/FILES +++ /dev/null @@ -1,5 +0,0 @@ -hello-world-local.cpp -HelloWorldLocal.java -dotest.sh -out.txt -ref.txt diff --git a/lowercasing_test/src/tests/hello-world/HelloWorldLocal.java b/lowercasing_test/src/tests/hello-world/HelloWorldLocal.java deleted file mode 100644 index a0928a58c99..00000000000 --- a/lowercasing_test/src/tests/hello-world/HelloWorldLocal.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -public class HelloWorldLocal -{ - public static void main(String[] args) - { - System.out.println("Java/local/Hello World"); - } -} diff --git a/lowercasing_test/src/tests/hello-world/dotest.sh b/lowercasing_test/src/tests/hello-world/dotest.sh deleted file mode 100755 index 53f66a84ffe..00000000000 --- a/lowercasing_test/src/tests/hello-world/dotest.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -set -e - -. ../../binref/env.sh - -./lowercasing_test_hello-world-local_app > out.txt -$HELLO_WORLD_APP >> out.txt -$BINREF/runjava HelloWorldLocal >> out.txt -$BINREF/runjava HelloWorld >> out.txt - -if diff -u out.txt ref.txt; then - exit 0 -else - exit 1 -fi diff --git a/lowercasing_test/src/tests/hello-world/hello-world-local.cpp b/lowercasing_test/src/tests/hello-world/hello-world-local.cpp deleted file mode 100644 index f9f42cf6b59..00000000000 --- a/lowercasing_test/src/tests/hello-world/hello-world-local.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include <vespa/fastos/fastos.h> -#include <grouping_test/hello-world-lib/hello-world.h> - -class App : public FastOS_Application -{ -public: - int Main(); -}; - -int -App::Main() -{ - HelloWorld::print(); - fprintf(stdout, "C++/local/Hello World\n"); - return 0; -} - -int -main(int argc, char **argv) -{ - App myapp; - return myapp.Entry(argc, argv); -} diff --git a/lowercasing_test/src/tests/hello-world/hello-world_test.sh b/lowercasing_test/src/tests/hello-world/hello-world_test.sh deleted file mode 100755 index f83dcbe2584..00000000000 --- a/lowercasing_test/src/tests/hello-world/hello-world_test.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -set -e -. ../../binref/env.sh -$BINREF/compilejava HelloWorldLocal.java -sh dotest.sh diff --git a/lowercasing_test/src/tests/hello-world/ref.txt b/lowercasing_test/src/tests/hello-world/ref.txt deleted file mode 100644 index 5f2f17f3e52..00000000000 --- a/lowercasing_test/src/tests/hello-world/ref.txt +++ /dev/null @@ -1,6 +0,0 @@ -C++/lib/Hello World -C++/local/Hello World -C++/lib/Hello World -C++/app/Hello World -Java/local/Hello World -Java/jar/HelloWorld diff --git a/node-admin/scripts/app.sh b/node-admin/scripts/app.sh index 8f1787118ed..83754413508 100755 --- a/node-admin/scripts/app.sh +++ b/node-admin/scripts/app.sh @@ -1,65 +1,6 @@ #!/bin/bash # Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -# BEGIN environment bootstrap section -# Do not edit between here and END as this section should stay identical in all scripts - -findpath () { - myname=${0} - mypath=${myname%/*} - myname=${myname##*/} - if [ "$mypath" ] && [ -d "$mypath" ]; then - return - fi - mypath=$(pwd) - if [ -f "${mypath}/${myname}" ]; then - return - fi - echo "FATAL: Could not figure out the path where $myname lives from $0" - exit 1 -} - -COMMON_ENV=libexec/vespa/common-env.sh - -source_common_env () { - if [ "$VESPA_HOME" ] && [ -d "$VESPA_HOME" ]; then - # ensure it ends with "/" : - VESPA_HOME=${VESPA_HOME%/}/ - export VESPA_HOME - common_env=$VESPA_HOME/$COMMON_ENV - if [ -f "$common_env" ]; then - . $common_env - return - fi - fi - return 1 -} - -findroot () { - source_common_env && return - if [ "$VESPA_HOME" ]; then - echo "FATAL: bad VESPA_HOME value '$VESPA_HOME'" - exit 1 - fi - if [ "$ROOT" ] && [ -d "$ROOT" ]; then - VESPA_HOME="$ROOT" - source_common_env && return - fi - findpath - while [ "$mypath" ]; do - VESPA_HOME=${mypath} - source_common_env && return - mypath=${mypath%/*} - done - echo "FATAL: missing VESPA_HOME environment variable" - echo "Could not locate $COMMON_ENV anywhere" - exit 1 -} - -findroot - -# END environment bootstrap section - set -e source "${0%/*}"/common.sh diff --git a/node-admin/scripts/configure-container-networking.py b/node-admin/scripts/configure-container-networking.py index 29ff6aa46ba..339621c13e1 100755 --- a/node-admin/scripts/configure-container-networking.py +++ b/node-admin/scripts/configure-container-networking.py @@ -70,44 +70,13 @@ def generate_mac_address(base_host_name, ip_address): mac_address = ':'.join('%02x' % n for n in mac_address_bytes) return mac_address - -flag_local_mode = "--local" -local_mode = flag_local_mode in sys.argv -if local_mode: - sys.argv.remove(flag_local_mode) - -flag_vm_mode = "--vm" -vm_mode = flag_vm_mode in sys.argv -if vm_mode: - sys.argv.remove(flag_vm_mode) - -if local_mode and vm_mode: - raise RuntimeError("Cannot specify both --local and --vm") - -if len(sys.argv) != 3: - raise RuntimeError("Usage: %s <container-pid> <ip>" % sys.argv[0]) - -container_pid_arg = sys.argv[1] -container_ip_arg = sys.argv[2] - -host_ns_name = "docker-host" -try: - container_pid = int(container_pid_arg) -except ValueError: - raise RuntimeError("Container pid must be an integer, got %s" % container_pid_arg) - -container_net_ns_path = net_namespace_path(container_pid) -if not os.path.isfile(container_net_ns_path): - raise RuntimeError("No such net namespace %s" % container_net_ns_path ) - -container_ip = ipaddress.ip_address(unicode(container_ip_arg)) - -create_directory_ignore_exists("/var/run/netns", 0766) -create_symlink_ignore_exists(net_namespace_path(1), "/var/run/netns/%s" % host_ns_name) -create_symlink_ignore_exists(container_net_ns_path, "/var/run/netns/%d" % container_pid) - -host_ns = NetNS(host_ns_name) -container_ns = NetNS(str(container_pid)) +def get_net_namespace_for_pid(pid): + net_ns_path = net_namespace_path(pid) + if not os.path.isfile(net_ns_path): + raise RuntimeError("No such net namespace %s" % net_ns_path ) + create_directory_ignore_exists("/var/run/netns", 0766) + create_symlink_ignore_exists(net_ns_path, "/var/run/netns/%d" % pid) + return NetNS(str(pid)) # ipv4 address format: { # 'index': 3, @@ -141,54 +110,35 @@ container_ns = NetNS(str(container_pid)) # 'scope': 0, # 'event': 'RTM_NEWADDR' # } -# Note: This only fetches ipv4 addresses -host_ips = host_ns.get_addr(family=AF_INET) - -host_ips_with_network_matching_container_ip = [host_ip for host_ip in host_ips if container_ip in network(host_ip)] +def ip_with_most_specific_network_for_address(address, ipv4_ips): + host_ips_with_network_matching_address = [host_ip for host_ip in ipv4_ips if address in network(host_ip)] -host_ip_best_match_for_container = None -for host_ip in host_ips_with_network_matching_container_ip: - if not host_ip_best_match_for_container: - host_ip_best_match_for_container = host_ip - elif host_ip['prefixlen'] < host_ip_best_match_for_container['prefixlen']: - host_ip_best_match_for_container = host_ip + host_ip_best_match_for_address = None + for host_ip in host_ips_with_network_matching_address: + if not host_ip_best_match_for_address: + host_ip_best_match_for_address = host_ip + elif host_ip['prefixlen'] < host_ip_best_match_for_address['prefixlen']: + host_ip_best_match_for_address = host_ip -if not host_ip_best_match_for_container: - raise RuntimeError("No matching ip address for %s, candidates are on networks %s" % (container_ip, ', '.join([str(network(host_ip)) for host_ip in host_ips]))) - -host_device_index_for_container = host_ip_best_match_for_container['index'] -container_network_prefix_length = host_ip_best_match_for_container['prefixlen'] + if not host_ip_best_match_for_address: + raise RuntimeError("No matching ip address for %s, candidates are on networks %s" % (address, ', '.join([str(network(host_ip)) for host_ip in ipv4_ips]))) + return host_ip_best_match_for_address ipr = IPRoute() +def delete_interface_by_name(interface_name): + for interface_index in ipr.link_lookup(ifname=interface_name): + ipr.link('delete', index=interface_index) -# Create new interface for the container. - -# The interface to the vespa network are all named "vespa". However, the -# container interfaces are prepared in the host network namespace, and so it -# needs a temporary name to avoid name-clash. -temporary_host_interface_name = "vespa-tmp-" + container_pid_arg -assert len(temporary_host_interface_name) <= 15 # linux requirement - -container_interface_name = "vespa" -assert len(container_interface_name) <= 15 # linux requirement - -for interface_index in ipr.link_lookup(ifname=temporary_host_interface_name): - ipr.link('delete', index=interface_index) - -if not container_ns.link_lookup(ifname=container_interface_name): - +def create_interface_in_namespace(network_namespace, ip_address_textual, interface_name, link_device_index): mac_address = generate_mac_address( - gethostname(), - container_ip_arg) + base_host_name=gethostname(), + ip_address=ip_address_textual) # For traceability. - with open('/tmp/container_mac_address_' + container_ip_arg, 'w') as f: + with open('/tmp/container_mac_address_' + ip_address_textual, 'w') as f: f.write(mac_address) - # Must be created in the host_ns to have the same lifetime as the host. - # Otherwise, it will be deleted when the node-admin container stops. - # (Only temporarily there, moved to the container namespace later.) # result = [{ # 'header': { # 'pid': 240, @@ -200,78 +150,67 @@ if not container_ns.link_lookup(ifname=container_interface_name): # }, # 'event': 'NLMSG_ERROR' # }] - # - # TODO: Here we're linking against the most_specific_address device. For - # the sake of argument, as of 2015-12-17, this device is always named - # 'vespa'. 'vespa' is itself a macvlan bridge linked to the default route's - # interface (typically eth0 or em1). So could we link against eth0 or em1 - # (or whatever) instead here? What's the difference? - result = host_ns.link_create( - ifname=temporary_host_interface_name, + result = network_namespace.link_create( + ifname=interface_name, kind='macvlan', - link=host_device_index_for_container, + link=link_device_index, macvlan_mode='bridge', address=mac_address) if result[0]['header']['error']: raise RuntimeError("Failed creating link, result = %s" % result ) - interface_index = host_ns.link_lookup(ifname=temporary_host_interface_name)[0] - - # Move interface from host namespace to container namespace, and change name from temporary name. - # exploit that node_admin docker container shares net namespace with host: - ipr.link('set', index=interface_index, net_ns_fd=str(container_pid), - ifname=container_interface_name) - - -# Find index of interface now in container namespace. - -container_interface_index_list = container_ns.link_lookup(ifname=container_interface_name) -if not container_interface_index_list: - raise RuntimeError("Concurrent modification to network interfaces in container") - -assert len(container_interface_index_list) == 1 -container_interface_index = container_interface_index_list[0] - - -# Set ip address on interface in container namespace. - -ip_already_configured = False - -for host_ip in container_ns.get_addr(index=container_interface_index, family = AF_INET): - if ipaddress.ip_address(unicode(get_attribute(host_ip, 'IFA_ADDRESS'))) == container_ip and host_ip['prefixlen'] == container_network_prefix_length: + index_of_created_interface = network_namespace.link_lookup(ifname=interface_name)[0] + return index_of_created_interface + +def index_of_interface_in_namespace(interface_name, namespace): + interface_index_list = namespace.link_lookup(ifname=interface_name) + if not interface_index_list: + return None + assert len(interface_index_list) == 1 + return interface_index_list[0] + +def move_interface(src_interface_index, dest_namespace, dest_namespace_pid, dest_interface_name): + ipr.link('set', + index=src_interface_index, + net_ns_fd=str(dest_namespace_pid), + ifname=dest_interface_name) + + new_interface_index = index_of_interface_in_namespace(interface_name=dest_interface_name, + namespace=dest_namespace) + if not new_interface_index: + raise RuntimeError("Concurrent modification to network interfaces") + return new_interface_index + +def set_ip_address(net_namespace, interface_index, ip_address, network_prefix_length): + ip_already_configured = False + + for existing_ip in net_namespace.get_addr(index=interface_index, family = AF_INET): + existing_ip_address = get_attribute(existing_ip, 'IFA_ADDRESS') + existing_ip_prefixlen = existing_ip['prefixlen'] + is_same_address = ipaddress.ip_address(unicode(existing_ip_address)) == ip_address + is_same_netmask = existing_ip_prefixlen == network_prefix_length + if is_same_address and is_same_netmask: ip_already_configured = True - else: - print("Deleting old ip address. %s/%s" % (get_attribute(host_ip, 'IFA_ADDRESS'), host_ip['prefixlen'])) - print(container_ns.addr('remove', - index=container_interface_index, - address=get_attribute(host_ip, 'IFA_ADDRESS'), - mask=host_ip['prefixlen'])) - -if not ip_already_configured: - try: - container_ns.addr('add', - index=container_interface_index, - address=str(container_ip), - #broadcast='192.168.59.255', - mask=container_network_prefix_length) - except NetlinkError as e: - if e.code == 17: # File exists, i.e. address is already added - pass - - -# Activate container interface. - -container_ns.link('set', index=container_interface_index, state='up', name=container_interface_name) - - -if local_mode: - pass -elif vm_mode: - # Set the default route to the IP of the host vespa interface (e.g. osx) - container_ns.route("add", gateway=get_attribute(host_ip_best_match_for_container, 'IFA_ADDRESS')) -else: - # Set up default route/gateway in container. - + else: + print("Deleting old ip address. %s/%s" % (existing_ip_address, existing_ip_prefixlen)) + result_of_remove = net_namespace.addr('remove', + index=interface_index, + address=existing_ip_address, + mask=existing_ip_prefixlen) + print(result_of_remove) + + if not ip_already_configured: + try: + net_namespace.addr('add', + index=interface_index, + address=str(ip_address), + # broadcast='192.168.59.255', + mask=network_prefix_length) + except NetlinkError as e: + if e.code == 17: # File exists, i.e. address is already added + pass + +def get_default_route(net_namespace): # route format: { # 'family': 2, # 'dst_len': 0, @@ -297,15 +236,117 @@ else: # 'type': 1, # 'scope': 0 # } - host_default_routes = host_ns.get_default_routes(family = AF_INET) - if len(host_default_routes) != 1: - raise RuntimeError("Couldn't find default route: " + str(host_default_routes)) - default_route = host_default_routes[0] + default_routes = net_namespace.get_default_routes(family = AF_INET) + if len(default_routes) != 1: + raise RuntimeError("Couldn't find single default route: " + str(default_routes)) + return default_routes[0] + + +# Parse arguments + +flag_local_mode = "--local" +local_mode = flag_local_mode in sys.argv +if local_mode: + sys.argv.remove(flag_local_mode) + +flag_vm_mode = "--vm" +vm_mode = flag_vm_mode in sys.argv +if vm_mode: + sys.argv.remove(flag_vm_mode) + +if local_mode and vm_mode: + raise RuntimeError("Cannot specify both --local and --vm") + +if len(sys.argv) != 3: + raise RuntimeError("Usage: %s <container-pid> <ip>" % sys.argv[0]) + +container_pid_arg = sys.argv[1] +container_ip_arg = sys.argv[2] + +try: + container_pid = int(container_pid_arg) +except ValueError: + raise RuntimeError("Container pid must be an integer, got %s" % container_pid_arg) +container_ip = ipaddress.ip_address(unicode(container_ip_arg)) + + +# Done parsing arguments, now let's get to work. + +host_ns = get_net_namespace_for_pid(1) +container_ns = get_net_namespace_for_pid(container_pid) + +all_host_ipv4_ips = host_ns.get_addr(family=AF_INET) +host_ip_best_match_for_container = ip_with_most_specific_network_for_address(address=container_ip, + ipv4_ips=all_host_ipv4_ips) +host_device_index_for_container = host_ip_best_match_for_container['index'] +container_network_prefix_length = host_ip_best_match_for_container['prefixlen'] + + +# Create new interface for the container. + +# The interface to the vespa network are all (in the end) named "vespa". However, +# the container interfaces are prepared in the host network namespace, and so they +# need temporary names to avoid name-clash. +temporary_interface_name_while_in_host_ns = "vespa-tmp-" + container_pid_arg +assert len(temporary_interface_name_while_in_host_ns) <= 15 # linux requirement + +container_interface_name = "vespa" +assert len(container_interface_name) <= 15 # linux requirement + +# Clean up any leftovers from the past. +delete_interface_by_name(temporary_interface_name_while_in_host_ns) + +container_interface_index = index_of_interface_in_namespace(interface_name=container_interface_name, + namespace=container_ns) +if not container_interface_index: + # Must be created in the host_ns to have the same lifetime as the host. + # Otherwise, it will be deleted when the node-admin container stops. + # (Only temporarily there, moved to the container namespace later.) + # + # TODO: Here we're linking against the device with the best matching network. + # For the sake of argument, as of 2015-12-17, this device is always named + # 'vespa'. 'vespa' is itself a macvlan bridge linked to the default route's + # interface (typically eth0 or em1). So could we link against eth0 or em1 + # (or whatever) instead here? What's the difference? + temporary_interface_index = create_interface_in_namespace(network_namespace=host_ns, + ip_address_textual=container_ip_arg, + interface_name=temporary_interface_name_while_in_host_ns, + link_device_index=host_device_index_for_container) + + # Move interface from host namespace to container namespace, and change name from temporary name. + # exploit that node_admin docker container shares net namespace with host: + container_interface_index = move_interface(src_interface_index=temporary_interface_index, + dest_namespace=container_ns, + dest_namespace_pid=container_pid, + dest_interface_name=container_interface_name) + + +# Set ip address on interface in container namespace. +set_ip_address(net_namespace=container_ns, + interface_index=container_interface_index, + ip_address=container_ip, + network_prefix_length=container_network_prefix_length) + + +# Activate container interface. + +container_ns.link('set', index=container_interface_index, state='up', name=container_interface_name) + + +if local_mode: + pass +elif vm_mode: + # Set the default route to the IP of the host vespa interface (e.g. osx) + # TODO: What about idempotency? This does not check for existing. Re-does work every time. + container_ns.route("add", gateway=get_attribute(host_ip_best_match_for_container, 'IFA_ADDRESS')) +else: + # Set up default route/gateway in container. + + host_default_route = get_default_route(net_namespace=host_ns) - host_default_route_device_index = get_attribute(default_route, 'RTA_OIF') - host_default_gateway = get_attribute(default_route, 'RTA_GATEWAY') + host_default_route_device_index = get_attribute(host_default_route, 'RTA_OIF') if host_device_index_for_container != host_default_route_device_index: raise RuntimeError("Container's ip address is not on the same network as the host's default route." " Could not set up default route for the container.") - container_gateway = host_default_gateway - container_ns.route("replace", gateway=container_gateway, index=container_interface_index) + host_default_route_gateway = get_attribute(host_default_route, 'RTA_GATEWAY') + container_ns.route("replace", gateway=host_default_route_gateway, index=container_interface_index) diff --git a/node-admin/scripts/node-admin.sh b/node-admin/scripts/node-admin.sh index cae3cf54d0d..405480e6d02 100755 --- a/node-admin/scripts/node-admin.sh +++ b/node-admin/scripts/node-admin.sh @@ -1,67 +1,6 @@ #!/bin/bash # Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -# BEGIN environment bootstrap section -# Do not edit between here and END as this section should stay identical in all scripts - -findpath () { - myname=${0} - mypath=${myname%/*} - myname=${myname##*/} - if [ "$mypath" ] && [ -d "$mypath" ]; then - return - fi - mypath=$(pwd) - if [ -f "${mypath}/${myname}" ]; then - return - fi - echo "FATAL: Could not figure out the path where $myname lives from $0" - exit 1 -} - -COMMON_ENV=libexec/vespa/common-env.sh - -source_common_env () { - if [ "$VESPA_HOME" ] && [ -d "$VESPA_HOME" ]; then - # ensure it ends with "/" : - VESPA_HOME=${VESPA_HOME%/}/ - export VESPA_HOME - common_env=$VESPA_HOME/$COMMON_ENV - if [ -f "$common_env" ]; then - . $common_env - return - fi - fi - return 1 -} - -findroot () { - source_common_env && return - if [ "$VESPA_HOME" ]; then - echo "FATAL: bad VESPA_HOME value '$VESPA_HOME'" - exit 1 - fi - if [ "$ROOT" ] && [ -d "$ROOT" ]; then - VESPA_HOME="$ROOT" - source_common_env && return - fi - findpath - while [ "$mypath" ]; do - VESPA_HOME=${mypath} - source_common_env && return - mypath=${mypath%/*} - done - echo "FATAL: missing VESPA_HOME environment variable" - echo "Could not locate $COMMON_ENV anywhere" - exit 1 -} - -findroot - -# END environment bootstrap section - -ROOT=$VESPA_HOME - set -e source "${0%/*}/common.sh" diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerFailTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerFailTest.java new file mode 100644 index 00000000000..9f0227e7b38 --- /dev/null +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerFailTest.java @@ -0,0 +1,107 @@ +package com.yahoo.vespa.hosted.node.admin.integrationTests; + + +import com.yahoo.vespa.applicationmodel.HostName; +import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; +import com.yahoo.vespa.hosted.node.admin.NodeAdmin; +import com.yahoo.vespa.hosted.node.admin.NodeAdminImpl; +import com.yahoo.vespa.hosted.node.admin.NodeAdminStateUpdater; +import com.yahoo.vespa.hosted.node.admin.NodeAgent; +import com.yahoo.vespa.hosted.node.admin.NodeAgentImpl; +import com.yahoo.vespa.hosted.node.admin.docker.ContainerName; +import com.yahoo.vespa.hosted.node.admin.docker.DockerImage; +import com.yahoo.vespa.hosted.node.admin.noderepository.NodeState; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Optional; +import java.util.function.Function; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +/** + * @author valerijf + */ +public class DockerFailTest { + private NodeRepoMock nodeRepositoryMock; + private DockerMock dockerMock; + private ContainerNodeSpec initialContainerNodeSpec; + private NodeAdminStateUpdater updater; + + @Before + public void before() throws InterruptedException { + try { + OrchestratorMock.semaphore.acquire(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + OrchestratorMock.reset(); + NodeRepoMock.reset(); + DockerMock.reset(); + + OrchestratorMock orchestratorMock = new OrchestratorMock(); + nodeRepositoryMock = new NodeRepoMock(); + dockerMock = new DockerMock(); + + Function<HostName, NodeAgent> nodeAgentFactory = (hostName) -> + new NodeAgentImpl(hostName, dockerMock, nodeRepositoryMock, orchestratorMock); + NodeAdmin nodeAdmin = new NodeAdminImpl(dockerMock, nodeAgentFactory); + + HostName hostName = new HostName("hostName"); + initialContainerNodeSpec = new ContainerNodeSpec( + hostName, + Optional.of(new DockerImage("dockerImage")), + new ContainerName("container"), + NodeState.ACTIVE, + Optional.of(1L), + Optional.of(1L), + Optional.of(1d), + Optional.of(1d), + Optional.of(1d)); + NodeRepoMock.addContainerNodeSpec(initialContainerNodeSpec); + + updater = new NodeAdminStateUpdater(nodeRepositoryMock, nodeAdmin, 1, 1, orchestratorMock, "basehostname"); + + // Wait for node admin to be notified with node repo state and the docker container has been started + while (nodeAdmin.getListOfHosts().size() == 0) { + Thread.sleep(10); + } + + while (!DockerMock.getRequests().startsWith("startContainer with DockerImage: DockerImage { imageId=dockerImage }, " + + "HostName: hostName, ContainerName: ContainerName { name=container }, minCpuCores: 1.0, minDiskAvailableGb: 1.0, " + + "minMainMemoryAvailableGb: 1.0\nexecuteInContainer with ContainerName: ContainerName { name=container }, " + + "args: [/usr/bin/env, test, -x, /opt/vespa/bin/vespa-nodectl]\nexecuteInContainer with ContainerName: " + + "ContainerName { name=container }, args: [/opt/vespa/bin/vespa-nodectl, resume]\n")) { + Thread.sleep(10); + } + } + + @After + public void after() { + updater.deconstruct(); + OrchestratorMock.semaphore.release(); + } + + @Test + public void dockerFailTest() throws InterruptedException { + dockerMock.deleteContainer(initialContainerNodeSpec.containerName); + + String goal = "startContainer with DockerImage: DockerImage { imageId=dockerImage }, HostName: hostName, " + + "ContainerName: ContainerName { name=container }, minCpuCores: 1.0, minDiskAvailableGb: 1.0, minMainMemoryAvailableGb: 1.0\n" + + "executeInContainer with ContainerName: ContainerName { name=container }, args: [/usr/bin/env, test, -x, /opt/vespa/bin/vespa-nodectl]\n" + + "executeInContainer with ContainerName: ContainerName { name=container }, args: [/opt/vespa/bin/vespa-nodectl, resume]\n" + + "deleteContainer with ContainerName: ContainerName { name=container }\n" + + "startContainer with DockerImage: DockerImage { imageId=dockerImage }, HostName: hostName, ContainerName: " + + "ContainerName { name=container }, minCpuCores: 1.0, minDiskAvailableGb: 1.0, minMainMemoryAvailableGb: 1.0\n" + + "executeInContainer with ContainerName: ContainerName { name=container }, args: [/usr/bin/env, test, -x, /opt/vespa/bin/vespa-nodectl]\n" + + "executeInContainer with ContainerName: ContainerName { name=container }, args: [/opt/vespa/bin/vespa-nodectl, resume]\n"; + while (!DockerMock.getRequests().equals(goal)) { + Thread.sleep(10); + } + + assertThat(DockerMock.getRequests(), is(goal)); + } +} diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerMock.java index 81de3783bb2..b6165a8d545 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerMock.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerMock.java @@ -102,7 +102,9 @@ public class DockerMock implements Docker { @Override public void deleteApplicationStorage(ContainerName containerName) throws IOException { - + synchronized (monitor) { + requests.append("deleteApplicationStorage with ContainerName: ").append(containerName).append("\n"); + } } @Override diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/MultiDockerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/MultiDockerTest.java new file mode 100644 index 00000000000..c61c690bb98 --- /dev/null +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/MultiDockerTest.java @@ -0,0 +1,151 @@ +package com.yahoo.vespa.hosted.node.admin.integrationTests; + +import com.yahoo.vespa.applicationmodel.HostName; +import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; +import com.yahoo.vespa.hosted.node.admin.NodeAdmin; +import com.yahoo.vespa.hosted.node.admin.NodeAdminImpl; +import com.yahoo.vespa.hosted.node.admin.NodeAdminStateUpdater; +import com.yahoo.vespa.hosted.node.admin.NodeAgent; +import com.yahoo.vespa.hosted.node.admin.NodeAgentImpl; +import com.yahoo.vespa.hosted.node.admin.docker.ContainerName; +import com.yahoo.vespa.hosted.node.admin.docker.DockerImage; +import com.yahoo.vespa.hosted.node.admin.noderepository.NodeState; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Optional; +import java.util.function.Function; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +/** + * @author valerijf + */ +public class MultiDockerTest { + private NodeRepoMock nodeRepositoryMock; + private DockerMock dockerMock; + private NodeAdmin nodeAdmin; + private NodeAdminStateUpdater updater; + + @Before + public void before() throws InterruptedException { + try { + OrchestratorMock.semaphore.acquire(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + OrchestratorMock.reset(); + NodeRepoMock.reset(); + DockerMock.reset(); + + OrchestratorMock orchestratorMock = new OrchestratorMock(); + nodeRepositoryMock = new NodeRepoMock(); + dockerMock = new DockerMock(); + + Function<HostName, NodeAgent> nodeAgentFactory = (hostName) -> + new NodeAgentImpl(hostName, dockerMock, nodeRepositoryMock, orchestratorMock); + nodeAdmin = new NodeAdminImpl(dockerMock, nodeAgentFactory); + updater = new NodeAdminStateUpdater(nodeRepositoryMock, nodeAdmin, 1, 1, orchestratorMock, "basehostname"); + } + + @After + public void after() { + updater.deconstruct(); + OrchestratorMock.semaphore.release(); + } + + @Test + public void test() throws InterruptedException, IOException { + addAndWaitForNode(new HostName("host1"), new ContainerName("container1"), Optional.of(new DockerImage("image1"))); + ContainerNodeSpec containerNodeSpec2 = + addAndWaitForNode(new HostName("host2"), new ContainerName("container2"), Optional.of(new DockerImage("image2"))); + + + NodeRepoMock.updateContainerNodeSpec( + containerNodeSpec2.hostname, + containerNodeSpec2.wantedDockerImage, + containerNodeSpec2.containerName, + NodeState.DIRTY, + containerNodeSpec2.wantedRestartGeneration, + containerNodeSpec2.currentRestartGeneration, + containerNodeSpec2.minCpuCores, + containerNodeSpec2.minMainMemoryAvailableGb, + containerNodeSpec2.minDiskAvailableGb); + + // Wait until it is marked ready + Optional<ContainerNodeSpec> tempContainerNodeSpec; + while ((tempContainerNodeSpec = nodeRepositoryMock.getContainerNodeSpec(containerNodeSpec2.hostname)).isPresent() + && tempContainerNodeSpec.get().nodeState != NodeState.READY) { + Thread.sleep(10); + } + + + addAndWaitForNode(new HostName("host3"), new ContainerName("container3"), Optional.of(new DockerImage("image1"))); + + assertThat(DockerMock.getRequests(), is( + "startContainer with DockerImage: DockerImage { imageId=image1 }, HostName: host1, ContainerName: ContainerName { name=container1 }, " + + "minCpuCores: 1.0, minDiskAvailableGb: 1.0, minMainMemoryAvailableGb: 1.0\n" + + "executeInContainer with ContainerName: ContainerName { name=container1 }, args: [/usr/bin/env, test, -x, /opt/vespa/bin/vespa-nodectl]\n" + + "executeInContainer with ContainerName: ContainerName { name=container1 }, args: [/opt/vespa/bin/vespa-nodectl, resume]\n" + + + "startContainer with DockerImage: DockerImage { imageId=image2 }, HostName: host2, ContainerName: ContainerName { name=container2 }, " + + "minCpuCores: 1.0, minDiskAvailableGb: 1.0, minMainMemoryAvailableGb: 1.0\n" + + "executeInContainer with ContainerName: ContainerName { name=container2 }, args: [/usr/bin/env, test, -x, /opt/vespa/bin/vespa-nodectl]\n" + + "executeInContainer with ContainerName: ContainerName { name=container2 }, args: [/opt/vespa/bin/vespa-nodectl, resume]\n" + + + "stopContainer with ContainerName: ContainerName { name=container2 }\n" + + "deleteContainer with ContainerName: ContainerName { name=container2 }\n" + + "deleteApplicationStorage with ContainerName: ContainerName { name=container2 }\n" + + + "startContainer with DockerImage: DockerImage { imageId=image1 }, HostName: host3, ContainerName: ContainerName { name=container3 }, " + + "minCpuCores: 1.0, minDiskAvailableGb: 1.0, minMainMemoryAvailableGb: 1.0\n" + + "executeInContainer with ContainerName: ContainerName { name=container3 }, args: [/usr/bin/env, test, -x, /opt/vespa/bin/vespa-nodectl]\n" + + "executeInContainer with ContainerName: ContainerName { name=container3 }, args: [/opt/vespa/bin/vespa-nodectl, resume]\n")); + + + String nodeRepoExpectedRequests = + "updateNodeAttributes with HostName: host1, restartGeneration: 1, DockerImage: DockerImage { imageId=image1 }, containerVespaVersion: null\n" + + "updateNodeAttributes with HostName: host2, restartGeneration: 1, DockerImage: DockerImage { imageId=image2 }, containerVespaVersion: null\n" + + "markAsReady with HostName: host2\n" + + "updateNodeAttributes with HostName: host3, restartGeneration: 1, DockerImage: DockerImage { imageId=image1 }, containerVespaVersion: null\n"; + + while (!NodeRepoMock.getRequests().equals(nodeRepoExpectedRequests)) { + Thread.sleep(10); + } + + assertThat(NodeRepoMock.getRequests(), is(nodeRepoExpectedRequests)); + } + + private ContainerNodeSpec addAndWaitForNode(HostName hostName, ContainerName containerName, Optional<DockerImage> dockerImage) throws InterruptedException { + ContainerNodeSpec containerNodeSpec = new ContainerNodeSpec( + hostName, + dockerImage, + containerName, + NodeState.ACTIVE, + Optional.of(1L), + Optional.of(1L), + Optional.of(1d), + Optional.of(1d), + Optional.of(1d)); + NodeRepoMock.addContainerNodeSpec(containerNodeSpec); + + // Wait for node admin to be notified with node repo state and the docker container has been started + while (nodeAdmin.getListOfHosts().size() != NodeRepoMock.getNumberOfContainerSpecs()) { + Thread.sleep(10); + } + + while (!DockerMock.getRequests().endsWith("startContainer with DockerImage: " + dockerImage.get() + ", " + + "HostName: " + hostName + ", ContainerName: " + containerName + ", minCpuCores: 1.0, minDiskAvailableGb: 1.0, " + + "minMainMemoryAvailableGb: 1.0\nexecuteInContainer with ContainerName: " + containerName + ", " + + "args: [/usr/bin/env, test, -x, /opt/vespa/bin/vespa-nodectl]\nexecuteInContainer with ContainerName: " + + containerName + ", args: [/opt/vespa/bin/vespa-nodectl, resume]\n")) { + Thread.sleep(10); + } + + return containerNodeSpec; + } +} diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java index d79079261cb..bffd6ddd22a 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java @@ -92,11 +92,8 @@ public class NodeRepoMock implements NodeRepository { } public static void addContainerNodeSpec(ContainerNodeSpec containerNodeSpec) { + removeContainerNodeSpec(containerNodeSpec.hostname); synchronized (monitor) { - containerNodeSpecs = containerNodeSpecs.stream() - .filter(c -> !c.hostname.equals(containerNodeSpec.hostname)) - .collect(Collectors.toList()); - containerNodeSpecs.add(containerNodeSpec); } } @@ -107,6 +104,20 @@ public class NodeRepoMock implements NodeRepository { } } + public static void removeContainerNodeSpec(HostName hostName) { + synchronized (monitor) { + containerNodeSpecs = containerNodeSpecs.stream() + .filter(c -> !c.hostname.equals(hostName)) + .collect(Collectors.toList()); + } + } + + public static int getNumberOfContainerSpecs() { + synchronized (monitor) { + return containerNodeSpecs.size(); + } + } + public static String getRequests() { synchronized (monitor) { return requests.toString(); diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeStateTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeStateTest.java index dba84f81cd3..76b6ca4533d 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeStateTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeStateTest.java @@ -92,37 +92,40 @@ public class NodeStateTest { } - @Test - public void activeToDirty() throws InterruptedException, IOException { - // Change node state to dirty - NodeRepoMock.updateContainerNodeSpec( - initialContainerNodeSpec.hostname, - initialContainerNodeSpec.wantedDockerImage, - initialContainerNodeSpec.containerName, - NodeState.DIRTY, - initialContainerNodeSpec.wantedRestartGeneration, - initialContainerNodeSpec.currentRestartGeneration, - initialContainerNodeSpec.minCpuCores, - initialContainerNodeSpec.minMainMemoryAvailableGb, - initialContainerNodeSpec.minDiskAvailableGb); - - // Wait until it is marked ready - Optional<ContainerNodeSpec> containerNodeSpec; - while ((containerNodeSpec = nodeRepositoryMock.getContainerNodeSpec(hostName)).isPresent() - && containerNodeSpec.get().nodeState != NodeState.READY) { - Thread.sleep(10); - } - - assertThat(nodeRepositoryMock.getContainerNodeSpec(hostName).get().nodeState, is(NodeState.READY)); - - - // Wait until docker receives deleteContainer request - while (!DockerMock.getRequests().endsWith("deleteContainer with ContainerName: ContainerName { name=container }\n")) { - Thread.sleep(10); - } - - assertThat(DockerMock.getRequests(), endsWith("deleteContainer with ContainerName: ContainerName { name=container }\n")); - } +// @Test +// public void activeToDirty() throws InterruptedException, IOException { +// // Change node state to dirty +// NodeRepoMock.updateContainerNodeSpec( +// initialContainerNodeSpec.hostname, +// initialContainerNodeSpec.wantedDockerImage, +// initialContainerNodeSpec.containerName, +// NodeState.DIRTY, +// initialContainerNodeSpec.wantedRestartGeneration, +// initialContainerNodeSpec.currentRestartGeneration, +// initialContainerNodeSpec.minCpuCores, +// initialContainerNodeSpec.minMainMemoryAvailableGb, +// initialContainerNodeSpec.minDiskAvailableGb); +// +// // Wait until it is marked ready +// Optional<ContainerNodeSpec> containerNodeSpec; +// while ((containerNodeSpec = nodeRepositoryMock.getContainerNodeSpec(hostName)).isPresent() +// && containerNodeSpec.get().nodeState != NodeState.READY) { +// Thread.sleep(10); +// } +// +// assertThat(nodeRepositoryMock.getContainerNodeSpec(hostName).get().nodeState, is(NodeState.READY)); +// +// +// // Wait until docker receives deleteContainer request +// String expectedDockerRequests = "stopContainer with ContainerName: ContainerName { name=container }\n" + +// "deleteContainer with ContainerName: ContainerName { name=container }\n" + +// "deleteApplicationStorage with ContainerName: ContainerName { name=container }\n"; +// while (!DockerMock.getRequests().endsWith(expectedDockerRequests)) { +// Thread.sleep(10); +// } +// +// assertThat(DockerMock.getRequests(), endsWith(expectedDockerRequests)); +// } @Test @@ -144,7 +147,9 @@ public class NodeStateTest { initialContainerNodeSpec.minMainMemoryAvailableGb, initialContainerNodeSpec.minDiskAvailableGb); - Thread.sleep(1000); + while (!initialDockerRequests.equals(DockerMock.getRequests())) { + Thread.sleep(10); + } assertThat(initialDockerRequests, is(DockerMock.getRequests())); diff --git a/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.sh b/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.sh index cfa706ac013..810f29f6d4f 100755 --- a/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.sh +++ b/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.sh @@ -1,5 +1,5 @@ #!/bin/bash set -e -export PWD=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd) +export PWD=$(pwd) $VALGRIND ./searchcore_verify_ranksetup_test_app diff --git a/searchcore/src/vespa/searchcore/fdispatch/program/fdispatch.cpp b/searchcore/src/vespa/searchcore/fdispatch/program/fdispatch.cpp index 6e06c91a902..0af1a7e3e2b 100644 --- a/searchcore/src/vespa/searchcore/fdispatch/program/fdispatch.cpp +++ b/searchcore/src/vespa/searchcore/fdispatch/program/fdispatch.cpp @@ -259,15 +259,15 @@ namespace { bool needRestart(const FdispatchrcConfig & curr, const FdispatchrcConfig & next) { if (curr.frtport != next.frtport) { - LOG(error, "FRT port has changed from %d to %d.", curr.frtport, next.frtport); + LOG(warning, "FRT port has changed from %d to %d.", curr.frtport, next.frtport); return true; } if (curr.ptport != next.ptport) { - LOG(error, "PT port has changed from %d to %d.", curr.ptport, next.ptport); + LOG(warning, "PT port has changed from %d to %d.", curr.ptport, next.ptport); return true; } if (curr.healthport != next.healthport) { - LOG(error, "Health port has changed from %d to %d.", curr.healthport, next.healthport); + LOG(warning, "Health port has changed from %d to %d.", curr.healthport, next.healthport); return true; } return false; @@ -280,7 +280,7 @@ void Fdispatch::configure(std::unique_ptr<FdispatchrcConfig> cfg) if (cfg && _config) { if ( needRestart(*_config, *cfg) ) { const int sleepMS = (1.0 + 30 * _rndGen.nextDouble()) * 1000; - LOG(error, "Will restart by abort in %d ms.", sleepMS); + LOG(warning, "Will restart by abort in %d ms.", sleepMS); std::this_thread::sleep_for(std::chrono::milliseconds(sleepMS)); _needRestart.store(true); } diff --git a/vespalib/src/tests/slaveproc/slaveproc_test.cpp b/vespalib/src/tests/slaveproc/slaveproc_test.cpp index 71427be6709..481c9a1da01 100644 --- a/vespalib/src/tests/slaveproc/slaveproc_test.cpp +++ b/vespalib/src/tests/slaveproc/slaveproc_test.cpp @@ -30,7 +30,7 @@ TEST("simple run, strip single-line trailing newline") { TEST("simple run, don't strip multi-line output") { std::string out; - EXPECT_TRUE(SlaveProc::run("echo -e \"foo\\n\"", out)); + EXPECT_TRUE(SlaveProc::run("perl -e 'print \"foo\\n\\n\"'", out)); EXPECT_EQUAL(out, "foo\n\n"); } |