# # # rename "tests/calculation_of_incorrect_unidiffs" # to "tests/calculation_of_other_unidiffs" # # add_dir "tests/creating_a_bad_criss-cross_merge" # # add_dir "tests/external_unit_test_of_the_line_merger" # # add_dir "tests/importing_CVS_files" # # add_dir "tests/importing_files_with_non-english_names" # # add_dir "tests/renaming_a_directory" # # add_dir "tests/renaming_a_file" # # add_dir "tests/renaming_and_editing_a_file" # # add_file "tests/creating_a_bad_criss-cross_merge/__driver__.lua" # content [1e60df2be65bc9164124f5ac8bc223cb11d34db4] # # add_file "tests/creating_a_bad_criss-cross_merge/alice.lua" # content [c29874140a27ed0d34c81f4270be3ef0a2d3e88f] # # add_file "tests/creating_a_bad_criss-cross_merge/bob.lua" # content [d3140fbd519c4fcc6d1dedd38b712f7f02431742] # # add_file "tests/external_unit_test_of_the_line_merger/__driver__.lua" # content [2ea0037fd766a52688bf3ff24709c9f9fea94c7f] # # add_file "tests/external_unit_test_of_the_line_merger/ancestor" # content [cec9ec2e479b700ea267e70feb5a4eb15155190d] # # add_file "tests/external_unit_test_of_the_line_merger/left" # content [52f65363d555fecd3d2e887a207c3add0a949638] # # add_file "tests/external_unit_test_of_the_line_merger/right" # content [3ea0b30aa5c7b20329ce9170ff4d379522c8bcda] # # add_file "tests/importing_CVS_files/__driver__.lua" # content [a63f2eb6c193e70c451e6d5fd0ae3a8c21ffa4f6] # # add_file "tests/importing_files_with_non-english_names/__driver__.lua" # content [b6d843c4a101c679ce29f26d4474836878a6da65] # # add_file "tests/renaming_a_directory/__driver__.lua" # content [d3136bb1f240bf8855276b841aa278f8f4c0869f] # # add_file "tests/renaming_a_file/__driver__.lua" # content [98a709e7e3a932a7e8fc1283aeb3b5cbaa9f2fbe] # # add_file "tests/renaming_and_editing_a_file/__driver__.lua" # content [83c3b6d4b1898ea545a62213725b7d82f348efd5] # # patch "tester.cc" # from [eda5f5ddd204e30c47fd9930f13ba19057a73b01] # to [da5d3a00ca842a183e2a6e61080e92cd630725f0] # # patch "tester.lua" # from [e466aa71c0adb51e51dacd015d7d54aa003500f0] # to [633fdce7b3d7b6da00b87b13ebc9783f86c42d02] # # patch "testsuite.at" # from [d87e4f1992dc6a6a58b5575317b401748758ee6e] # to [4c90fa9da36842d426e761cbd3c5164ec15a157b] # # patch "testsuite.lua" # from [65bc566aa260fd09e30fbfe41566adfb434d9ea1] # to [af091d9e1c6478f69881ed2290fcc9aa197e2e74] # ============================================================ --- tests/creating_a_bad_criss-cross_merge/__driver__.lua 1e60df2be65bc9164124f5ac8bc223cb11d34db4 +++ tests/creating_a_bad_criss-cross_merge/__driver__.lua 1e60df2be65bc9164124f5ac8bc223cb11d34db4 @@ -0,0 +1,87 @@ + +mtn_setup() + +-- this test demonstrates a tricky case in which two parties, bob and +-- alice, merge a fork, left and right, differently. bob chooses the +-- changes in the left node, alice chooses the changes in the right +-- node. +-- +-- the result of merging their merges incorrectly succeeds, considering +-- the LCA(bob,alice) as either foo or bar, and thereby seeing one of +-- the edges (left->bob or right->alice) as having "no changes", and +-- letting the edge "with changes" (right->bob, or left->alice) clobber +-- it. +-- +-- this should be fixed so the merge-of-merges conflicts. + +writefile("shared.anc", "base version data") +writefile("shared.left", "conflicting data on left side") +writefile("shared.right", "conflicting data on right side") +writefile("specific.left", "non-conflicting mergeable data on left side") +writefile("specific.right", "non-conflicting mergeable data on right side") +writefile("specific.alice", "non-conflicting mergeable data in bob") +writefile("specific.bob", "non-conflicting mergeable data in alice") + +-- this case is somewhat tricky to set up too... we use two different +-- keys (bob and alice) that don't trust each other so that they can +-- produce two different merge results + +getfile("bob.lua") +getfile("alice.lua") + +function bob (...) + return raw_mtn("--rcfile=test_hooks.lua", "--rcfile=bob.lua", + "--nostd", "--norc", "--db=test.db", "--key=bob", + "--keydir=keys", unpack(arg)) +end +function alice (...) + return raw_mtn("--rcfile=test_hooks.lua", "--rcfile=alice.lua", + "--nostd", "--norc", "--db=test.db", "--key=alice", + "--keydir=keys", unpack(arg)) +end + +check(cmd(bob("genkey", "bob")), 0, false, false, "bob\nbob\n") +check(cmd(alice("genkey", "alice")), 0, false, false, "alice\nalice\n") + + +-- construct ancestor +copyfile("shared.anc", "shared") +addfile("shared") +commit() +root_r_sha = base_revision() +root_f_sha = sha1("shared") + +-- construct left node +copyfile("shared.left", "shared") +addfile("specific.left") +commit() +left_r_sha = base_revision() +left_f_sha = sha1("shared") +check(left_r_sha ~= root_r_sha) +check(left_f_sha ~= root_f_sha) + +-- revert to root +revert_to(root_r_sha) + +-- construct right node +copyfile("shared.right", "shared") +addfile("specific.right") +commit() +right_r_sha = base_revision() +right_f_sha = sha1("shared") +check(right_r_sha ~= root_r_sha) +check(right_f_sha ~= root_f_sha) +check(right_r_sha ~= left_r_sha) +check(right_f_sha ~= left_f_sha) + +-- construct alice, a merge choosing the right side to win +check(cmd(alice("merge")), 0, false, false) + +-- construct bob, a merge choosing the left side to win +check(cmd(bob("merge")), 0, false, false) + +-- now merge the merges. this *should* fail. +-- because there are conflicting changes and +-- we have no merge3 hook to fall back on + +check(cmd(mtn("merge")), 1, false, false) ============================================================ --- tests/creating_a_bad_criss-cross_merge/alice.lua c29874140a27ed0d34c81f4270be3ef0a2d3e88f +++ tests/creating_a_bad_criss-cross_merge/alice.lua c29874140a27ed0d34c81f4270be3ef0a2d3e88f @@ -0,0 +1,14 @@ +function merge3(anc_path, left_path, right_path, merged_path, ancestor, left, right) + return right +end + +function get_passphrase(key) return "alice" end + +function get_author(branch) return "alice" end + +function get_revision_cert_trust(signers, id, name, val) + for k,v in pairs(signers) do + if (v ~= "bob") then return true end + end + return false +end ============================================================ --- tests/creating_a_bad_criss-cross_merge/bob.lua d3140fbd519c4fcc6d1dedd38b712f7f02431742 +++ tests/creating_a_bad_criss-cross_merge/bob.lua d3140fbd519c4fcc6d1dedd38b712f7f02431742 @@ -0,0 +1,14 @@ +function merge3(anc_path, left_path, right_path, merged_path, ancestor, left, right) + return left +end + +function get_passphrase(key) return "bob" end + +function get_author(branch) return "bob" end + +function get_revision_cert_trust(signers, id, name, val) + for k,v in pairs(signers) do + if (v ~= "alice") then return true end + end + return false +end ============================================================ --- tests/external_unit_test_of_the_line_merger/__driver__.lua 2ea0037fd766a52688bf3ff24709c9f9fea94c7f +++ tests/external_unit_test_of_the_line_merger/__driver__.lua 2ea0037fd766a52688bf3ff24709c9f9fea94c7f @@ -0,0 +1,34 @@ + +skip_if(not existsonpath("diff3")) + +mtn_setup() + +getfile("left") +getfile("right") +getfile("ancestor") + +anc = "cec9ec2e479b700ea267e70feb5a4eb15155190d" +left = "52f65363d555fecd3d2e887a207c3add0a949638" +right = "3ea0b30aa5c7b20329ce9170ff4d379522c8bcda" + +aver = sha1("ancestor") +lver = sha1("left") +rver = sha1("right") + +check(aver == anc) +check(lver == left) +check(rver == right) + +copyfile("ancestor", "stdin") +check(cmd(mtn("fload")), 0, false, false, true) +copyfile("left", "stdin") +check(cmd(mtn("fload")), 0, false, false, true) +copyfile("right", "stdin") +check(cmd(mtn("fload")), 0, false, false, true) + +check(cmd(mtn("fmerge", anc, left, right)), 0, true, false) +canonicalize("stdout") +rename("stdout", "merge.monotone") + +-- we expect the output to be the same as the right file in this case +check(samefile("merge.monotone", "right")) ============================================================ --- tests/external_unit_test_of_the_line_merger/ancestor cec9ec2e479b700ea267e70feb5a4eb15155190d +++ tests/external_unit_test_of_the_line_merger/ancestor cec9ec2e479b700ea267e70feb5a4eb15155190d @@ -0,0 +1,329 @@ +#include +#include +#include + +#include +#include +#include + +#include + +#include "cryptopp/arc4.h" +#include "cryptopp/base64.h" +#include "cryptopp/hex.h" +#include "cryptopp/cryptlib.h" +#include "cryptopp/osrng.h" +#include "cryptopp/sha.h" +#include "cryptopp/rsa.h" + +#include "constants.hh" +#include "keys.hh" +#include "lua.hh" +#include "transforms.hh" +#include "sanity.hh" + +// copyright (C) 2002, 2003 graydon hoare +// all rights reserved. +// licensed to the public under the terms of the GNU GPL (>= 2) +// see the file COPYING for details + +// there will probably forever be bugs in this file. it's very +// hard to get right, portably and securely. sorry about that. + +using namespace CryptoPP; +using namespace std; + +using boost::shared_ptr; + +static void do_arc4(SecByteBlock & phrase, + SecByteBlock & payload) +{ + L(F("running arc4 process on %d bytes of data\n") % payload.size()); + ARC4 a4(phrase.data(), phrase.size()); + a4.ProcessString(payload.data(), payload.size()); +} + +static void read_passphrase(lua_hooks & lua, + rsa_keypair_id const & keyid, + SecByteBlock & phrase) +{ + string lua_phrase; + + // we permit the user to relax security here, by caching a passphrase (if + // they permit it) through the life of a program run. this helps when + // you're making a half-dozen certs during a commit or merge or + // something. + bool persist_phrase = lua.hook_persist_phrase_ok();; + static std::map phrases; + + if (persist_phrase && phrases.find(keyid) != phrases.end()) + { + string phr = phrases[keyid]; + phrase.Assign(reinterpret_cast(phr.data()), phr.size()); + return; + } + + + if (lua.hook_get_passphrase(keyid, lua_phrase)) + { + // user is being a slob and hooking lua to return his passphrase + phrase.Assign(reinterpret_cast(lua_phrase.data()), + lua_phrase.size()); + } + else + { + // FIXME: we will drop extra bytes of their phrase + size_t bufsz = 4096; + char buf[bufsz]; + + // out to the console for us! + // FIXME: this is *way* non-portable at the moment. + cout << "enter passphrase for key ID [" << keyid() << "] : "; + cout.flush(); + + int cin_fd = 0; + struct termios t, t_saved; + tcgetattr(cin_fd, &t); + t_saved = t; + t.c_lflag &= ~ECHO; + tcsetattr(cin_fd, TCSANOW, &t); + + try + { + tcsetattr(cin_fd, TCSANOW, &t); + cin.getline(buf, bufsz, '\n'); + phrase.Assign(reinterpret_cast(buf), strlen(buf)); + + // permit security relaxation. maybe. + if (persist_phrase) + { + phrases.insert(make_pair(keyid,string(buf))); + } + } + catch (...) + { + memset(buf, 0, bufsz); + tcsetattr(cin_fd, TCSANOW, &t_saved); + cout << endl; + throw; + } + cout << endl; + memset(buf, 0, bufsz); + tcsetattr(cin_fd, TCSANOW, &t_saved); + } +} + +template +static void write_der(T & val, SecByteBlock & sec) +{ + // FIXME: this helper is *wrong*. I don't see now to DER-encode into a + // SecByteBlock, so we may well wind up leaving raw key bytes in malloc + // regions if we're not lucky. but we want to. maybe muck with + // AllocatorWithCleanup? who knows.. please fix! + string der_encoded; + try + { + StringSink der_sink(der_encoded); + val.DEREncode(der_sink); + der_sink.MessageEnd(); + sec.Assign(reinterpret_cast(der_encoded.data()), + der_encoded.size()); + L(F("wrote %d bytes of DER-encoded data\n") % der_encoded.size()); + } + catch (...) + { + for (size_t i = 0; i < der_encoded.size(); ++i) + der_encoded[i] = '\0'; + throw; + } + for (size_t i = 0; i < der_encoded.size(); ++i) + der_encoded[i] = '\0'; +} + + +void generate_key_pair(lua_hooks & lua, // to hook for phrase + rsa_keypair_id const & id, // to prompting user for phrase + base64 & pub_out, + base64< arc4 > & priv_out) +{ + // we will panic here if the user doesn't like urandom and we can't give + // them a real entropy-driven random. + bool request_blocking_rng = false; + if (!lua.hook_non_blocking_rng_ok()) + { +#ifndef BLOCKING_RNG_AVAILABLE + throw oops("no blocking RNG available and non-blocking RNG rejected"); +#else + request_blocking_rng = true; +#endif + } + + AutoSeededRandomPool rng(request_blocking_rng); + SecByteBlock phrase, pubkey, privkey; + rsa_pub_key raw_pub_key; + arc4 raw_priv_key; + + // generate private key (and encrypt it) + RSAES_OAEP_SHA_Decryptor priv(rng, keylen); + write_der(priv, privkey); + read_passphrase(lua, id, phrase); + do_arc4(phrase, privkey); + raw_priv_key = string(reinterpret_cast(privkey.data()), + privkey.size()); + + // generate public key + RSAES_OAEP_SHA_Encryptor pub(priv); + write_der(pub, pubkey); + raw_pub_key = string(reinterpret_cast(pubkey.data()), + pubkey.size()); + + // if all that worked, we can return our results to caller + encode_base64(raw_priv_key, priv_out); + encode_base64(raw_pub_key, pub_out); + L(F("generated %d-byte public key\n") % pub_out().size()); + L(F("generated %d-byte (encrypted) private key\n") % priv_out().size()); +} + +void make_signature(lua_hooks & lua, // to hook for phrase + rsa_keypair_id const & id, // to prompting user for phrase + base64< arc4 > const & priv, + string const & tosign, + base64 & signature) +{ + arc4 decoded_key; + SecByteBlock decrypted_key; + SecByteBlock phrase; + string sig_string; + + // we will panic here if the user doesn't like urandom and we can't give + // them a real entropy-driven random. + bool request_blocking_rng = false; + if (!lua.hook_non_blocking_rng_ok()) + { +#ifndef BLOCKING_RNG_AVAILABLE + throw oops("no blocking RNG available and non-blocking RNG rejected"); +#else + request_blocking_rng = true; +#endif + } + AutoSeededRandomPool rng(request_blocking_rng); + + // we permit the user to relax security here, by caching a decrypted key + // (if they permit it) through the life of a program run. this helps when + // you're making a half-dozen certs during a commit or merge or + // something. + + static std::map > signers; + bool persist_phrase = (!signers.empty()) || lua.hook_persist_phrase_ok();; + + shared_ptr signer; + if (persist_phrase + && signers.find(id) != signers.end()) + signer = signers[id]; + + else + { + L(F("base64-decoding %d-byte private key\n") % priv().size()); + decode_base64(priv, decoded_key); + decrypted_key.Assign(reinterpret_cast(decoded_key().data()), + decoded_key().size()); + read_passphrase(lua, id, phrase); + do_arc4(phrase, decrypted_key); + + L(F("building signer from %d-byte decrypted private key\n") % decrypted_key.size()); + StringSource keysource(decrypted_key.data(), decrypted_key.size(), true); + signer = shared_ptr + (new RSASSA_PKCS1v15_SHA_Signer(keysource)); + + if (persist_phrase) + signers.insert(make_pair(id,signer)); + } + + StringSource tmp(tosign, true, + new SignerFilter + (rng, *signer, + new StringSink(sig_string))); + + L(F("produced %d-byte signature\n") % sig_string.size()); + encode_base64(rsa_sha1_signature(sig_string), signature); +} + +bool check_signature(base64 const & pub_encoded, + string const & alleged_text, + base64 const & signature) +{ + // examine pubkey + rsa_pub_key pub; + decode_base64(pub_encoded, pub); + SecByteBlock pub_block; + pub_block.Assign(reinterpret_cast(pub().data()), pub().size()); + StringSource keysource(pub_block.data(), pub_block.size(), true); + L(F("building verifier for %d-byte pub key\n") % pub_block.size()); + RSASSA_PKCS1v15_SHA_Verifier verifier(keysource); + + // examine signature + rsa_sha1_signature sig_decoded; + decode_base64(signature, sig_decoded); + if (sig_decoded().size() != verifier.SignatureLength()) + return false; + + // check the text+sig against the key + L(F("checking %d-byte (%d decoded) signature with %d-byte pub key\n") % + signature().size() % sig_decoded().size() % pub_block.size()); + VerifierFilter * vf = NULL; + + // crypto++ likes to use pointers in ways which boost and std:: smart + // pointers aren't really good with, unfortunately. + try + { + vf = new VerifierFilter(verifier); + vf->Put(reinterpret_cast(sig_decoded().data()), sig_decoded().size()); + } + catch (...) + { + if (vf) + delete vf; + throw; + } + + I(vf); + StringSource tmp(alleged_text, true, vf); + return vf->GetLastResult(); +} + + +#ifdef BUILD_UNIT_TESTS +#include "unit_tests.hh" + +static void signature_round_trip_test() +{ + lua_hooks lua; + lua.add_std_hooks(); + lua.add_test_hooks(); + + BOOST_CHECKPOINT("generating key pairs"); + rsa_keypair_id key("address@hidden"); + base64 pubkey; + base64< arc4 > privkey; + generate_key_pair(lua, key, pubkey, privkey); + + BOOST_CHECKPOINT("signing plaintext"); + string plaintext("test string to sign"); + base64 sig; + make_signature(lua, key, privkey, plaintext, sig); + + BOOST_CHECKPOINT("checking signature"); + BOOST_CHECK(check_signature(pubkey, plaintext, sig)); + + string broken_plaintext = plaintext + " ...with a lie"; + BOOST_CHECKPOINT("checking non-signature"); + BOOST_CHECK(!check_signature(pubkey, broken_plaintext, sig)); +} + +void add_key_tests(test_suite * suite) +{ + I(suite); + suite->add(BOOST_TEST_CASE(&signature_round_trip_test)); +} + +#endif // BUILD_UNIT_TESTS ============================================================ --- tests/external_unit_test_of_the_line_merger/left 52f65363d555fecd3d2e887a207c3add0a949638 +++ tests/external_unit_test_of_the_line_merger/left 52f65363d555fecd3d2e887a207c3add0a949638 @@ -0,0 +1,355 @@ +#include +#include +#include + +#include +#include +#include + +#include + +#include "cryptopp/arc4.h" +#include "cryptopp/base64.h" +#include "cryptopp/hex.h" +#include "cryptopp/cryptlib.h" +#include "cryptopp/osrng.h" +#include "cryptopp/sha.h" +#include "cryptopp/rsa.h" + +#include "constants.hh" +#include "keys.hh" +#include "lua.hh" +#include "transforms.hh" +#include "sanity.hh" + +// copyright (C) 2002, 2003 graydon hoare +// all rights reserved. +// licensed to the public under the terms of the GNU GPL (>= 2) +// see the file COPYING for details + +// there will probably forever be bugs in this file. it's very +// hard to get right, portably and securely. sorry about that. + +using namespace CryptoPP; +using namespace std; + +using boost::shared_ptr; + +static void do_arc4(SecByteBlock & phrase, + SecByteBlock & payload) +{ + L(F("running arc4 process on %d bytes of data\n") % payload.size()); + ARC4 a4(phrase.data(), phrase.size()); + a4.ProcessString(payload.data(), payload.size()); +} + +static void read_passphrase(lua_hooks & lua, + rsa_keypair_id const & keyid, + SecByteBlock & phrase) +{ + string lua_phrase; + + // we permit the user to relax security here, by caching a passphrase (if + // they permit it) through the life of a program run. this helps when + // you're making a half-dozen certs during a commit or merge or + // something. + bool persist_phrase = lua.hook_persist_phrase_ok();; + static std::map phrases; + + if (persist_phrase && phrases.find(keyid) != phrases.end()) + { + string phr = phrases[keyid]; + phrase.Assign(reinterpret_cast(phr.data()), phr.size()); + return; + } + + + if (lua.hook_get_passphrase(keyid, lua_phrase)) + { + // user is being a slob and hooking lua to return his passphrase + phrase.Assign(reinterpret_cast(lua_phrase.data()), + lua_phrase.size()); + } + else + { + // FIXME: we will drop extra bytes of their phrase + size_t bufsz = 4096; + char buf[bufsz]; + + // out to the console for us! + // FIXME: this is *way* non-portable at the moment. + cout << "enter passphrase for key ID [" << keyid() << "] : "; + cout.flush(); + + int cin_fd = 0; + struct termios t, t_saved; + tcgetattr(cin_fd, &t); + t_saved = t; + t.c_lflag &= ~ECHO; + tcsetattr(cin_fd, TCSANOW, &t); + + try + { + tcsetattr(cin_fd, TCSANOW, &t); + cin.getline(buf, bufsz, '\n'); + phrase.Assign(reinterpret_cast(buf), strlen(buf)); + + // permit security relaxation. maybe. + if (persist_phrase) + { + phrases.insert(make_pair(keyid,string(buf))); + } + } + catch (...) + { + memset(buf, 0, bufsz); + tcsetattr(cin_fd, TCSANOW, &t_saved); + cout << endl; + throw; + } + cout << endl; + memset(buf, 0, bufsz); + tcsetattr(cin_fd, TCSANOW, &t_saved); + } +} + +template +static void write_der(T & val, SecByteBlock & sec) +{ + // FIXME: this helper is *wrong*. I don't see now to DER-encode into a + // SecByteBlock, so we may well wind up leaving raw key bytes in malloc + // regions if we're not lucky. but we want to. maybe muck with + // AllocatorWithCleanup? who knows.. please fix! + string der_encoded; + try + { + StringSink der_sink(der_encoded); + val.DEREncode(der_sink); + der_sink.MessageEnd(); + sec.Assign(reinterpret_cast(der_encoded.data()), + der_encoded.size()); + L(F("wrote %d bytes of DER-encoded data\n") % der_encoded.size()); + } + catch (...) + { + for (size_t i = 0; i < der_encoded.size(); ++i) + der_encoded[i] = '\0'; + throw; + } + for (size_t i = 0; i < der_encoded.size(); ++i) + der_encoded[i] = '\0'; +} + + +void generate_key_pair(lua_hooks & lua, // to hook for phrase + rsa_keypair_id const & id, // to prompting user for phrase + base64 & pub_out, + base64< arc4 > & priv_out) +{ + // we will panic here if the user doesn't like urandom and we can't give + // them a real entropy-driven random. + bool request_blocking_rng = false; + if (!lua.hook_non_blocking_rng_ok()) + { +#ifndef BLOCKING_RNG_AVAILABLE + throw oops("no blocking RNG available and non-blocking RNG rejected"); +#else + request_blocking_rng = true; +#endif + } + + AutoSeededRandomPool rng(request_blocking_rng); + SecByteBlock phrase, pubkey, privkey; + rsa_pub_key raw_pub_key; + arc4 raw_priv_key; + + // generate private key (and encrypt it) + RSAES_OAEP_SHA_Decryptor priv(rng, keylen); + write_der(priv, privkey); + read_passphrase(lua, id, phrase); + do_arc4(phrase, privkey); + raw_priv_key = string(reinterpret_cast(privkey.data()), + privkey.size()); + + // generate public key + RSAES_OAEP_SHA_Encryptor pub(priv); + write_der(pub, pubkey); + raw_pub_key = string(reinterpret_cast(pubkey.data()), + pubkey.size()); + + // if all that worked, we can return our results to caller + encode_base64(raw_priv_key, priv_out); + encode_base64(raw_pub_key, pub_out); + L(F("generated %d-byte public key\n") % pub_out().size()); + L(F("generated %d-byte (encrypted) private key\n") % priv_out().size()); +} + +void make_signature(lua_hooks & lua, // to hook for phrase + rsa_keypair_id const & id, // to prompting user for phrase + base64< arc4 > const & priv, + string const & tosign, + base64 & signature) +{ + arc4 decoded_key; + SecByteBlock decrypted_key; + SecByteBlock phrase; + string sig_string; + + // we will panic here if the user doesn't like urandom and we can't give + // them a real entropy-driven random. + bool request_blocking_rng = false; + if (!lua.hook_non_blocking_rng_ok()) + { +#ifndef BLOCKING_RNG_AVAILABLE + throw oops("no blocking RNG available and non-blocking RNG rejected"); +#else + request_blocking_rng = true; +#endif + } + AutoSeededRandomPool rng(request_blocking_rng); + + // we permit the user to relax security here, by caching a decrypted key + // (if they permit it) through the life of a program run. this helps when + // you're making a half-dozen certs during a commit or merge or + // something. + + static std::map > signers; + bool persist_phrase = (!signers.empty()) || lua.hook_persist_phrase_ok();; + + shared_ptr signer; + if (persist_phrase + && signers.find(id) != signers.end()) + signer = signers[id]; + + else + { + L(F("base64-decoding %d-byte private key\n") % priv().size()); + decode_base64(priv, decoded_key); + decrypted_key.Assign(reinterpret_cast(decoded_key().data()), + decoded_key().size()); + read_passphrase(lua, id, phrase); + do_arc4(phrase, decrypted_key); + + try + { + L(F("building signer from %d-byte decrypted private key\n") % decrypted_key.size()); + StringSource keysource(decrypted_key.data(), decrypted_key.size(), true); + signer = shared_ptr + (new RSASSA_PKCS1v15_SHA_Signer(keysource)); + } + catch (...) + { + throw informative_failure("failed to decrypt private RSA key, " + "probably incorrect passphrase"); + } + + if (persist_phrase) + signers.insert(make_pair(id,signer)); + } + + StringSource tmp(tosign, true, + new SignerFilter + (rng, *signer, + new StringSink(sig_string))); + + L(F("produced %d-byte signature\n") % sig_string.size()); + encode_base64(rsa_sha1_signature(sig_string), signature); +} + +bool check_signature(lua_hooks & lua, + rsa_keypair_id const & id, + base64 const & pub_encoded, + string const & alleged_text, + base64 const & signature) +{ + // examine pubkey + + static std::map > verifiers; + bool persist_phrase = (!verifiers.empty()) || lua.hook_persist_phrase_ok(); + + shared_ptr verifier; + if (persist_phrase + && verifiers.find(id) != verifiers.end()) + verifier = verifiers[id]; + + else + { + rsa_pub_key pub; + decode_base64(pub_encoded, pub); + SecByteBlock pub_block; + pub_block.Assign(reinterpret_cast(pub().data()), pub().size()); + StringSource keysource(pub_block.data(), pub_block.size(), true); + L(F("building verifier for %d-byte pub key\n") % pub_block.size()); + verifier = shared_ptr + (new RSASSA_PKCS1v15_SHA_Verifier(keysource)); + + if (persist_phrase) + verifiers.insert(make_pair(id, verifier)); + } + + // examine signature + rsa_sha1_signature sig_decoded; + decode_base64(signature, sig_decoded); + if (sig_decoded().size() != verifier->SignatureLength()) + return false; + + // check the text+sig against the key + L(F("checking %d-byte (%d decoded) signature\n") % + signature().size() % sig_decoded().size()); + VerifierFilter * vf = NULL; + + // crypto++ likes to use pointers in ways which boost and std:: smart + // pointers aren't really good with, unfortunately. + try + { + vf = new VerifierFilter(*verifier); + vf->Put(reinterpret_cast(sig_decoded().data()), sig_decoded().size()); + } + catch (...) + { + if (vf) + delete vf; + throw; + } + + I(vf); + StringSource tmp(alleged_text, true, vf); + return vf->GetLastResult(); +} + + +#ifdef BUILD_UNIT_TESTS +#include "unit_tests.hh" + +static void signature_round_trip_test() +{ + lua_hooks lua; + lua.add_std_hooks(); + lua.add_test_hooks(); + + BOOST_CHECKPOINT("generating key pairs"); + rsa_keypair_id key("address@hidden"); + base64 pubkey; + base64< arc4 > privkey; + generate_key_pair(lua, key, pubkey, privkey); + + BOOST_CHECKPOINT("signing plaintext"); + string plaintext("test string to sign"); + base64 sig; + make_signature(lua, key, privkey, plaintext, sig); + + BOOST_CHECKPOINT("checking signature"); + BOOST_CHECK(check_signature(lua, key, pubkey, plaintext, sig)); + + string broken_plaintext = plaintext + " ...with a lie"; + BOOST_CHECKPOINT("checking non-signature"); + BOOST_CHECK(!check_signature(lua, key, pubkey, broken_plaintext, sig)); +} + +void add_key_tests(test_suite * suite) +{ + I(suite); + suite->add(BOOST_TEST_CASE(&signature_round_trip_test)); +} + +#endif // BUILD_UNIT_TESTS ============================================================ --- tests/external_unit_test_of_the_line_merger/right 3ea0b30aa5c7b20329ce9170ff4d379522c8bcda +++ tests/external_unit_test_of_the_line_merger/right 3ea0b30aa5c7b20329ce9170ff4d379522c8bcda @@ -0,0 +1,355 @@ +#include +#include +#include + +#include +#include +#include + +#include + +#include "cryptopp/arc4.h" +#include "cryptopp/base64.h" +#include "cryptopp/hex.h" +#include "cryptopp/cryptlib.h" +#include "cryptopp/osrng.h" +#include "cryptopp/sha.h" +#include "cryptopp/rsa.h" + +#include "constants.hh" +#include "keys.hh" +#include "lua.hh" +#include "transforms.hh" +#include "sanity.hh" + +// copyright (C) 2002, 2003 graydon hoare +// all rights reserved. +// licensed to the public under the terms of the GNU GPL (>= 2) +// see the file COPYING for details + +// there will probably forever be bugs in this file. it's very +// hard to get right, portably and securely. sorry about that. + +using namespace CryptoPP; +using namespace std; + +using boost::shared_ptr; + +static void do_arc4(SecByteBlock & phrase, + SecByteBlock & payload) +{ + L(F("running arc4 process on %d bytes of data\n") % payload.size()); + ARC4 a4(phrase.data(), phrase.size()); + a4.ProcessString(payload.data(), payload.size()); +} + +static void read_passphrase(lua_hooks & lua, + rsa_keypair_id const & keyid, + SecByteBlock & phrase) +{ + string lua_phrase; + + // we permit the user to relax security here, by caching a passphrase (if + // they permit it) through the life of a program run. this helps when + // you're making a half-dozen certs during a commit or merge or + // something. + bool persist_phrase = lua.hook_persist_phrase_ok();; + static std::map phrases; + + if (persist_phrase && phrases.find(keyid) != phrases.end()) + { + string phr = phrases[keyid]; + phrase.Assign(reinterpret_cast(phr.data()), phr.size()); + return; + } + + + if (lua.hook_get_passphrase(keyid, lua_phrase)) + { + // user is being a slob and hooking lua to return his passphrase + phrase.Assign(reinterpret_cast(lua_phrase.data()), + lua_phrase.size()); + } + else + { + // FIXME: we will drop extra bytes of their phrase + size_t bufsz = 4096; + char buf[bufsz]; + + // out to the console for us! + // FIXME: this is *way* non-portable at the moment. + cout << "enter passphrase for key ID [" << keyid() << "] : "; + cout.flush(); + + int cin_fd = 0; + struct termios t, t_saved; + tcgetattr(cin_fd, &t); + t_saved = t; + t.c_lflag &= ~ECHO; + tcsetattr(cin_fd, TCSANOW, &t); + + try + { + tcsetattr(cin_fd, TCSANOW, &t); + cin.getline(buf, bufsz, '\n'); + phrase.Assign(reinterpret_cast(buf), strlen(buf)); + + // permit security relaxation. maybe. + if (persist_phrase) + { + phrases.insert(make_pair(keyid,string(buf))); + } + } + catch (...) + { + memset(buf, 0, bufsz); + tcsetattr(cin_fd, TCSANOW, &t_saved); + cout << endl; + throw; + } + cout << endl; + memset(buf, 0, bufsz); + tcsetattr(cin_fd, TCSANOW, &t_saved); + } +} + +template +static void write_der(T & val, SecByteBlock & sec) +{ + // FIXME: this helper is *wrong*. I don't see now to DER-encode into a + // SecByteBlock, so we may well wind up leaving raw key bytes in malloc + // regions if we're not lucky. but we want to. maybe muck with + // AllocatorWithCleanup? who knows.. please fix! + string der_encoded; + try + { + StringSink der_sink(der_encoded); + val.DEREncode(der_sink); + der_sink.MessageEnd(); + sec.Assign(reinterpret_cast(der_encoded.data()), + der_encoded.size()); + L(F("wrote %d bytes of DER-encoded data\n") % der_encoded.size()); + } + catch (...) + { + for (size_t i = 0; i < der_encoded.size(); ++i) + der_encoded[i] = '\0'; + throw; + } + for (size_t i = 0; i < der_encoded.size(); ++i) + der_encoded[i] = '\0'; +} + + +void generate_key_pair(lua_hooks & lua, // to hook for phrase + rsa_keypair_id const & id, // to prompting user for phrase + base64 & pub_out, + base64< arc4 > & priv_out) +{ + // we will panic here if the user doesn't like urandom and we can't give + // them a real entropy-driven random. + bool request_blocking_rng = false; + if (!lua.hook_non_blocking_rng_ok()) + { +#ifndef BLOCKING_RNG_AVAILABLE + throw oops("no blocking RNG available and non-blocking RNG rejected"); +#else + request_blocking_rng = true; +#endif + } + + AutoSeededRandomPool rng(request_blocking_rng); + SecByteBlock phrase, pubkey, privkey; + rsa_pub_key raw_pub_key; + arc4 raw_priv_key; + + // generate private key (and encrypt it) + RSAES_OAEP_SHA_Decryptor priv(rng, constants::keylen); + write_der(priv, privkey); + read_passphrase(lua, id, phrase); + do_arc4(phrase, privkey); + raw_priv_key = string(reinterpret_cast(privkey.data()), + privkey.size()); + + // generate public key + RSAES_OAEP_SHA_Encryptor pub(priv); + write_der(pub, pubkey); + raw_pub_key = string(reinterpret_cast(pubkey.data()), + pubkey.size()); + + // if all that worked, we can return our results to caller + encode_base64(raw_priv_key, priv_out); + encode_base64(raw_pub_key, pub_out); + L(F("generated %d-byte public key\n") % pub_out().size()); + L(F("generated %d-byte (encrypted) private key\n") % priv_out().size()); +} + +void make_signature(lua_hooks & lua, // to hook for phrase + rsa_keypair_id const & id, // to prompting user for phrase + base64< arc4 > const & priv, + string const & tosign, + base64 & signature) +{ + arc4 decoded_key; + SecByteBlock decrypted_key; + SecByteBlock phrase; + string sig_string; + + // we will panic here if the user doesn't like urandom and we can't give + // them a real entropy-driven random. + bool request_blocking_rng = false; + if (!lua.hook_non_blocking_rng_ok()) + { +#ifndef BLOCKING_RNG_AVAILABLE + throw oops("no blocking RNG available and non-blocking RNG rejected"); +#else + request_blocking_rng = true; +#endif + } + AutoSeededRandomPool rng(request_blocking_rng); + + // we permit the user to relax security here, by caching a decrypted key + // (if they permit it) through the life of a program run. this helps when + // you're making a half-dozen certs during a commit or merge or + // something. + + static std::map > signers; + bool persist_phrase = (!signers.empty()) || lua.hook_persist_phrase_ok();; + + shared_ptr signer; + if (persist_phrase + && signers.find(id) != signers.end()) + signer = signers[id]; + + else + { + L(F("base64-decoding %d-byte private key\n") % priv().size()); + decode_base64(priv, decoded_key); + decrypted_key.Assign(reinterpret_cast(decoded_key().data()), + decoded_key().size()); + read_passphrase(lua, id, phrase); + do_arc4(phrase, decrypted_key); + + try + { + L(F("building signer from %d-byte decrypted private key\n") % decrypted_key.size()); + StringSource keysource(decrypted_key.data(), decrypted_key.size(), true); + signer = shared_ptr + (new RSASSA_PKCS1v15_SHA_Signer(keysource)); + } + catch (...) + { + throw informative_failure("failed to decrypt private RSA key, " + "probably incorrect passphrase"); + } + + if (persist_phrase) + signers.insert(make_pair(id,signer)); + } + + StringSource tmp(tosign, true, + new SignerFilter + (rng, *signer, + new StringSink(sig_string))); + + L(F("produced %d-byte signature\n") % sig_string.size()); + encode_base64(rsa_sha1_signature(sig_string), signature); +} + +bool check_signature(lua_hooks & lua, + rsa_keypair_id const & id, + base64 const & pub_encoded, + string const & alleged_text, + base64 const & signature) +{ + // examine pubkey + + static std::map > verifiers; + bool persist_phrase = (!verifiers.empty()) || lua.hook_persist_phrase_ok(); + + shared_ptr verifier; + if (persist_phrase + && verifiers.find(id) != verifiers.end()) + verifier = verifiers[id]; + + else + { + rsa_pub_key pub; + decode_base64(pub_encoded, pub); + SecByteBlock pub_block; + pub_block.Assign(reinterpret_cast(pub().data()), pub().size()); + StringSource keysource(pub_block.data(), pub_block.size(), true); + L(F("building verifier for %d-byte pub key\n") % pub_block.size()); + verifier = shared_ptr + (new RSASSA_PKCS1v15_SHA_Verifier(keysource)); + + if (persist_phrase) + verifiers.insert(make_pair(id, verifier)); + } + + // examine signature + rsa_sha1_signature sig_decoded; + decode_base64(signature, sig_decoded); + if (sig_decoded().size() != verifier->SignatureLength()) + return false; + + // check the text+sig against the key + L(F("checking %d-byte (%d decoded) signature\n") % + signature().size() % sig_decoded().size()); + VerifierFilter * vf = NULL; + + // crypto++ likes to use pointers in ways which boost and std:: smart + // pointers aren't really good with, unfortunately. + try + { + vf = new VerifierFilter(*verifier); + vf->Put(reinterpret_cast(sig_decoded().data()), sig_decoded().size()); + } + catch (...) + { + if (vf) + delete vf; + throw; + } + + I(vf); + StringSource tmp(alleged_text, true, vf); + return vf->GetLastResult(); +} + + +#ifdef BUILD_UNIT_TESTS +#include "unit_tests.hh" + +static void signature_round_trip_test() +{ + lua_hooks lua; + lua.add_std_hooks(); + lua.add_test_hooks(); + + BOOST_CHECKPOINT("generating key pairs"); + rsa_keypair_id key("address@hidden"); + base64 pubkey; + base64< arc4 > privkey; + generate_key_pair(lua, key, pubkey, privkey); + + BOOST_CHECKPOINT("signing plaintext"); + string plaintext("test string to sign"); + base64 sig; + make_signature(lua, key, privkey, plaintext, sig); + + BOOST_CHECKPOINT("checking signature"); + BOOST_CHECK(check_signature(lua, key, pubkey, plaintext, sig)); + + string broken_plaintext = plaintext + " ...with a lie"; + BOOST_CHECKPOINT("checking non-signature"); + BOOST_CHECK(!check_signature(lua, key, pubkey, broken_plaintext, sig)); +} + +void add_key_tests(test_suite * suite) +{ + I(suite); + suite->add(BOOST_TEST_CASE(&signature_round_trip_test)); +} + +#endif // BUILD_UNIT_TESTS ============================================================ --- tests/importing_CVS_files/__driver__.lua a63f2eb6c193e70c451e6d5fd0ae3a8c21ffa4f6 +++ tests/importing_CVS_files/__driver__.lua a63f2eb6c193e70c451e6d5fd0ae3a8c21ffa4f6 @@ -0,0 +1,59 @@ + +mtn_setup() + +skip_if(not existsonpath("cvs")) + +writefile("importme.0", "version 0 of test file") + +writefile("importme.1", "version 1 of test file") + +writefile("importme.2", "version 2 of test file") + +writefile("importme.3", "version 3 of test file") + +tsha0=sha1("importme.0") +tsha1=sha1("importme.1") +tsha2=sha1("importme.2") +tsha3=sha1("importme.3") + +-- build the cvs repository + +cvsroot = test_root .. "/cvs-repository" +check(cmd("cvs", "-q", "-d", cvsroot, "init"), 0, false, false) +check(exists(cvsroot)) +check(exists(cvsroot .. "/CVSROOT")) +check(exists(cvsroot .. "/CVSROOT/modules")) + +-- check out the workspace and make some commits +-- note that this has to use copyfile, rather than rename, to update +-- the file in cvs. Apparently, cvs uses timestamps or something to track +-- file modifications. +check(cmd("cvs", "-d", cvsroot, "co", "."), 0, false, false) +mkdir("testdir") +copyfile("importme.0", "testdir/importme") +check(cmd("cvs", "-d", cvsroot, "add", "testdir"), 0, false, false) +check(cmd("cvs", "-d", cvsroot, "add", "testdir/importme"), 0, false, false) +check(cmd("cvs", "-d", cvsroot, "commit", "-m", 'commit 0', "testdir/importme"), 0, false, false) +copyfile("importme.1", "testdir/importme") +check(cmd("cvs", "-d", cvsroot, "commit", "-m", 'commit 1', "testdir/importme"), 0, false, false) +copyfile("importme.2", "testdir/importme") +check(cmd("cvs", "-d", cvsroot, "commit", "-m", 'commit 2', "testdir/importme"), 0, false, false) +copyfile("importme.3", "testdir/importme") +check(cmd("cvs", "-d", cvsroot, "commit", "-m", 'commit 3', "testdir/importme"), 0, false, false) + +-- import into monotone and check presence of files + +-- safety check -- we stop people from accidentally feeding their whole +-- repo to cvs_import instead of just a module. +check(cmd(mtn("--branch=testbranch", "cvs_import", cvsroot)), 1, false, false) +check(cmd(mtn("--branch=testbranch", "cvs_import", cvsroot .. "/testdir")), 0, false, false) +check(cmd(mtn("automate", "get_file", tsha0)), 0, false) +check(cmd(mtn("automate", "get_file", tsha1)), 0, false) +check(cmd(mtn("automate", "get_file", tsha2)), 0, false) +check(cmd(mtn("automate", "get_file", tsha3)), 0, false) + +-- also check that history is okay -- has a unique head, and it's the +-- right one. + +check(cmd(mtn("checkout", "--branch=testbranch", "mtcodir")), 0, false, false) +check(samefile("importme.3", "mtcodir/importme")) ============================================================ --- tests/importing_files_with_non-english_names/__driver__.lua b6d843c4a101c679ce29f26d4474836878a6da65 +++ tests/importing_files_with_non-english_names/__driver__.lua b6d843c4a101c679ce29f26d4474836878a6da65 @@ -0,0 +1,113 @@ + +mtn_setup() + +european_utf8 = "\195\182\195\164\195\188\195\159" + -- "\xC3\xB6\xC3\xA4\xC3\xBc\xC3\x9F" +european_8859_1 = "\246\228\252\223" + -- "\xF6\xE4\xFC\xDF" + +japanese_utf8 = "\227\129\166\227\129\153\227\129\168" + -- "\xE3\x81\xA6\xE3\x81\x99\xE3\x81\xA8" +japanese_euc_jp = "\164\198\164\185\164\200" + -- "\xA4\xC6\xA4\xB9\xA4\xC8" + +if ostype == "Windows" then + funny_filename = "address@hidden" +else + funny_filename = "address@hidden:" +end + +for _,name in pairs{"weird", "utf8", "8859-1", "euc"} do + mkdir(name) +end +check(writefile("weird/file name with spaces", "")) +check(writefile("weird/" .. funny_filename, "")) +check(writefile("utf8/" .. european_utf8, "")) +check(writefile("utf8/" .. japanese_utf8, "")) + +if ostype ~= "Darwin" then + check(writefile("8859-1/" .. european_8859_1, "")) + check(writefile("euc/" .. japanese_euc_jp, "")) +end + +check(cmd(mtn("add", "weird/file name with spaces")), 0, false, false) +check(cmd(mtn("add", "weird/" .. funny_filename)), 0, false, false) + +-- add some files with UTF8 names +set_env("LANG", "en_US.utf-8") +set_env("CHARSET", "UTF-8") +check(cmd(mtn("add", "utf8/" .. european_utf8)), 0, false, false) +check(cmd(mtn("add", "utf8/" .. japanese_utf8)), 0, false, false) + +commit() + +-- check the names showed up in our manifest + +set_env("LANG", "en_US.utf-8") +set_env("CHARSET", "UTF-8") + +check(cmd(mtn("automate", "get_manifest_of")), 0, true) +os.rename("stdout", "manifest") +check(qgrep("funny", "manifest")) +check(qgrep("spaces", "manifest")) +check(qgrep(japanese_utf8, "manifest")) +check(qgrep(european_utf8, "manifest")) + +-- okay, now we try in two different locales. monotone is happy to +-- have arbirary utf8 filenames in it, but these locales don't support +-- arbitrary utf8 -- you have to use a utf8 locale if you want to put +-- filenames on your disk in utf8. if we keep all the utf8 files in +-- the tree, then, monotone will attempt to convert them to the current +-- locale, and fail miserably. so get rid of them first. + +check(cmd(mtn("drop", "utf8/" .. european_utf8, "utf8/" .. japanese_utf8)), 0, false, false) +commit() + +-- OS X expects data passed to the OS to be utf8, so these tests don't make +-- sense. +if ostype ~= "Darwin" then + -- now try iso-8859-1 + + set_env("LANG", "de_DE.iso-8859-1") + set_env("CHARSET", "iso-8859-1") + check(cmd(mtn("add", "8859-1/" .. european_8859_1)), 0, false, false) + + commit() +end + +-- check the names showed up in our manifest + +check(cmd(mtn("automate", "get_manifest_of")), 0, true) +os.rename("stdout", "manifest") +check(qgrep("funny", "manifest")) +check(qgrep("spaces", "manifest")) +if ostype ~= "Darwin" then + check(qgrep("8859-1/" .. european_utf8, "manifest")) +end + +-- okay, clean up again + +if ostype ~= "Darwin" then + check(cmd(mtn("drop", "8859-1/" .. european_8859_1)), 0, false, false) + commit() +end + +-- now try euc + +if ostype ~= "Darwin" then + set_env("LANG", "ja_JP.euc-jp") + set_env("CHARSET", "euc-jp") + check(cmd(mtn("add", "euc/" .. japanese_euc_jp)), 0, false, false) + + commit() +end + +-- check the names showed up in our manifest + +check(cmd(mtn("automate", "get_manifest_of")), 0, true) +os.rename("stdout", "manifest") +check(qgrep("funny", "manifest")) +check(qgrep("spaces", "manifest")) +if ostype ~= "Darwin" then + check(qgrep("euc/" .. japanese_utf8, "manifest")) +end ============================================================ --- tests/renaming_a_directory/__driver__.lua d3136bb1f240bf8855276b841aa278f8f4c0869f +++ tests/renaming_a_directory/__driver__.lua d3136bb1f240bf8855276b841aa278f8f4c0869f @@ -0,0 +1,38 @@ + +mtn_setup() + +mkdir("foo") + +writefile("foo/foo", "foo file") +writefile("bleh", "bleh file") + +-- produce root +check(cmd(mtn("add", "foo")), 0, false, false) +commit() +root_r_sha = base_revision() +root_f_sha = sha1("foo/foo") + +-- produce move edge +check(cmd(mtn("rename", "foo", "bar")), 0, false, false) +os.rename("foo", "bar") +commit() + +-- revert to root +probe_node("foo/foo", root_r_sha, root_f_sha) +remove_recursive("bar") + +-- make an add *into the directory* +addfile("foo/bar", "bar file") +commit() + +-- merge the add and the rename +check(cmd(mtn("merge")), 0, false, false) +check(cmd(mtn("update")), 0, false, false) +check(cmd(mtn("automate", "get_manifest_of")), 0, true, false) +os.rename("stdout", "manifest") +check(qgrep("bar/bar", "manifest")) +check(qgrep("bar/foo", "manifest")) +check(not qgrep("foo/bar", "manifest")) +check(not qgrep("foo/foo", "manifest")) +check(exists("bar/bar")) +check(exists("bar/foo")) ============================================================ --- tests/renaming_a_file/__driver__.lua 98a709e7e3a932a7e8fc1283aeb3b5cbaa9f2fbe +++ tests/renaming_a_file/__driver__.lua 98a709e7e3a932a7e8fc1283aeb3b5cbaa9f2fbe @@ -0,0 +1,61 @@ + +mtn_setup() + +writefile("foo", "foo file") +writefile("bleh", "bleh file") + +-- produce root +addfile("foo") +commit() +root_r_sha = base_revision() +root_f_sha = sha1("foo") + +-- produce move edge +check(cmd(mtn("rename", "foo", "bar")), 0, false, false) +copyfile("foo", "bar") +commit() + +-- revert to root +probe_node("foo", root_r_sha, root_f_sha) +os.remove("bar") + +-- make a simple add edge +addfile("bleh") +commit() + +-- merge the add and the rename +check(cmd(mtn("merge")), 0, false, false) +check(cmd(mtn("update")), 0, false, false) +check(cmd(mtn("automate", "get_manifest_of")), 0, true, false) +os.rename("stdout", "manifest") +check(qgrep("bar", "manifest")) +check(qgrep("bleh", "manifest")) + +-- rename a rename target +check(cmd(mtn("rename", "bleh", "blah")), 0, false, false) +check(qgrep("bleh", "_MTN/work")) +check(qgrep("blah", "_MTN/work")) +check(cmd(mtn("rename", "blah", "blyh")), 0, false, false) +check(qgrep("bleh", "_MTN/work")) +check(not qgrep("blah", "_MTN/work")) +check(qgrep("blyh", "_MTN/work")) + +-- undo a rename +check(cmd(mtn("rename", "blyh", "bleh")), 0, false, false) +check(not exists("_MTN/work")) + +-- move file before renaming it +check(cmd(mtn("status")), 0, false, false) +os.rename("bar", "barfoo") +check(cmd(mtn("rename", "bar", "barfoo")), 0, false, true) +check(qgrep('renaming bar to barfoo in workspace manifest', "stderr")) +check(cmd(mtn("status")), 0, false, false) + +-- move file to wrong place before renaming it +os.rename("barfoo", "bar") +check(cmd(mtn("revert", ".")), 0, false, false) +check(cmd(mtn("status")), 0, false, false) +os.rename("bar", "barfoofoo") +check(cmd(mtn("rename", "bar", "barfoo")), 0, false, true) +check(qgrep('renaming bar to barfoo in workspace manifest', "stderr")) +check(cmd(mtn("status")), 1, false, false) ============================================================ --- tests/renaming_and_editing_a_file/__driver__.lua 83c3b6d4b1898ea545a62213725b7d82f348efd5 +++ tests/renaming_and_editing_a_file/__driver__.lua 83c3b6d4b1898ea545a62213725b7d82f348efd5 @@ -0,0 +1,47 @@ + +mtn_setup() + +writefile("foo1", "foo file 1") +writefile("foo2", "foo file 2") +writefile("bar1", "bar file 1") +writefile("bar2", "bar file 2") +writefile("bleh", "bleh file") + +-- produce root +os.rename("foo1", "foo") +check(cmd(mtn("add", "foo")), 0, false, false) +check(cmd(mtn("--branch=testbranch", "commit", "--message=root")), 0, false, false) +root_r_sha=base_revision() +root_f_sha=sha1("foo") + +-- produce 4-step path with move in the middle +os.rename("foo2", "foo") +check(cmd(mtn("commit", "--message=edit-foo")), 0, false, false) +check(cmd(mtn("rename", "foo", "bar")), 0, false, false) +os.rename("bar1", "bar") +check(cmd(mtn("commit", "--message=rename-to-bar")), 0, false, false) +os.rename("bar2", "bar") +check(cmd(mtn("commit", "--message=edit-bar")), 0, false, false) + +-- revert to root +probe_node("foo", root_r_sha, root_f_sha) +os.remove("bar") + +-- make a simple add edge +check(cmd(mtn("add", "bleh")), 0, false, false) +check(cmd(mtn("commit", "--message=blah-blah")), 0, false, false) + +-- merge the add and the rename +check(cmd(mtn("merge")), 0, false, false) +check(cmd(mtn("update")), 0, false, false) +check(cmd(mtn("automate", "get_manifest_of")), 0, true, false) +os.rename("stdout", "manifest") +check(qgrep("bar", "manifest")) +check(qgrep("bleh", "manifest")) +check(not qgrep("foo", "manifest")) + +-- now the moment of truth: do we *think* there was a rename? +check(cmd(mtn("diff", "--revision", root_r_sha)), 0, true, false) +check(qgrep("rename", "stdout")) + +os.remove("bar") ============================================================ --- tester.cc eda5f5ddd204e30c47fd9930f13ba19057a73b01 +++ tester.cc da5d3a00ca842a183e2a6e61080e92cd630725f0 @@ -8,6 +8,7 @@ #include "lua.hh" #include "tester.h" #include "paths.hh" +#include "platform.hh" #include @@ -18,7 +19,12 @@ #include #include +#include +#include + using std::string; +using std::map; +using std::make_pair; using boost::lexical_cast; @@ -31,6 +37,57 @@ #include #endif +#include +map orig_env_vars; +void save_env() { orig_env_vars.clear(); } +#ifdef WIN32 +void restore_env() +{ + for (map::const_iterator i = orig_env_vars.begin(); + i != orig_env_vars.end(); ++i) + { + _putenv_s(i->first.c_str(), i->second.c_str()); + } + orig_env_vars.clear(); +} +void set_env(string const &var, string const &val) +{ + char const *old = getenv(var.c_str()); + if (old) + orig_env_vars.insert(make_pair(var, string(old))); + else + orig_env_vars.insert(make_pair(var, "")) + _putenv_s(var.c_str(), val.c_str()); +} +#else +void putenv2(string const &var, string const &val) +{ + char const *s = (var + "=" + val).c_str(); + size_t len = var.size() + val.size() + 2; + char *cp = new char[len]; + memcpy(cp, s, len); + putenv(cp); +} +void restore_env() +{ + for (map::const_iterator i = orig_env_vars.begin(); + i != orig_env_vars.end(); ++i) + { + putenv2(i->first, i->second); + } + orig_env_vars.clear(); +} +void set_env(string const &var, string const &val) +{ + char const *old = getenv(var.c_str()); + if (old) + orig_env_vars.insert(make_pair(var, string(old))); + else + orig_env_vars.insert(make_pair(var, "")); + putenv2(var, val); +} +#endif + int set_redirect(int what, string where, string mode) { int saved = dup(what); @@ -163,6 +220,38 @@ return 0; } + + static int + get_ostype(lua_State * L) + { + string str; + get_system_flavour(str); + lua_pushstring(L, str.c_str()); + return 1; + } + + static int + do_save_env(lua_State * L) + { + save_env(); + return 0; + } + + static int + do_restore_env(lua_State * L) + { + restore_env(); + return 0; + } + + static int + do_set_env(lua_State * L) + { + char const * var = luaL_checkstring(L, -2); + char const * val = luaL_checkstring(L, -1); + set_env(var, val); + return 0; + } } int main(int argc, char **argv) @@ -212,6 +301,10 @@ lua_register(st, "mkdir", make_dir); lua_register(st, "remove_recursive", remove_recursive); lua_register(st, "exists", exists); + lua_register(st, "get_ostype", get_ostype); + lua_register(st, "save_env", do_save_env); + lua_register(st, "restore_env", do_restore_env); + lua_register(st, "set_env", do_set_env); int ret = 2; try ============================================================ --- tester.lua e466aa71c0adb51e51dacd015d7d54aa003500f0 +++ tester.lua 633fdce7b3d7b6da00b87b13ebc9783f86c42d02 @@ -12,6 +12,48 @@ test_log = nil -- logfile for this test failed_testlogs = {} +function P(...) + io.write(unpack(arg)) + io.flush() + logfile:write(unpack(arg)) +end + +function L(...) + test_log:write(unpack(arg)) +end + +function getsrcline() + local info + local depth = 1 + repeat + depth = depth + 1 + info = debug.getinfo(depth) + until info == nil + while src == nil and depth > 1 do + depth = depth - 1 + info = debug.getinfo(depth) + if string.find(info.source, "address@hidden") then + -- return info.source, info.currentline + return testname, info.currentline + end + end +end + +function locheader() + local _,line = getsrcline() + if testname == nil then + return "\n:" .. line .. ": " + else + return "\n" .. testname .. ":" .. line .. ": " + end +end + +old_mkdir = mkdir +mkdir = function(name) + L(locheader(), "mkdir ", name, "\n") + old_mkdir(name) +end + function fsize(filename) local file = io.open(filename, "r") if file == nil then error("Cannot open file " .. filename, 2) end @@ -21,30 +63,40 @@ end function readfile(filename) + L(locheader(), "readfile ", filename, "\n") local file = io.open(filename, "rb") - if file == nil then error("Cannot open file " .. filename, 2) end + if file == nil then + error("Cannot open file " .. filename) + end local dat = file:read("*a") file:close() return dat end function writefile(filename, dat) + L(locheader(), "writefile ", filename, "\n") local file = io.open(filename, "wb") - if file == nil then error("Cannot open file " .. filename, 2) end + if file == nil then + L("Cannot open file " .. filename) + return false + end file:write(dat) file:close() - return + return true end function copyfile(from, to) + L(locheader(), "copyfile ", from, " ", to, "\n") local infile = io.open(from, "rb") if infile == nil then - error("Cannot open file " .. from, 2) + L("Cannot open file " .. from) + return false end local outfile = io.open(to, "wb") if outfile == nil then infile:close() - error("Cannot open file " .. to, 2) + L("Cannot open file " .. to) + return false end local size = 2^13 while true do @@ -54,8 +106,36 @@ end infile:close() outfile:close() + return true end +function rename(from, to) + L(locheader(), "rename ", from, " ", to, "\n") + local res,err = os.rename(from, to) + if res == nil then + L(err, "\n") + return false + else + return true + end +end + +function remove(file) + L(locheader(), "remove ", file, "\n") + local res,err = os.remove(file) + if res == nil then + L(err, "\n") + return false + else + return true + end +end + +function rename_over(from, to) + remove(to) + return rename(from, to) +end + function getstdfile(name, as) copyfile(srcdir .. "/" .. name, as) end @@ -77,28 +157,9 @@ return ret end -function getsrcline() - local info - local depth = 1 - repeat - depth = depth + 1 - info = debug.getinfo(depth) - until info == nil - while src == nil and depth > 1 do - depth = depth - 1 - info = debug.getinfo(depth) - if string.find(info.source, "address@hidden") then - -- return info.source, info.currentline - return testname, info.currentline - end - end -end - function cmd(first, ...) - local _1,_2 = getsrcline() - local src = _1 .. ":" .. _2 if type(first) == "string" then - test_log:write("\n", src, ": ", first, " ", table.concat(arg, " "), "\n") + L(locheader(), first, " ", table.concat(arg, " "), "\n") return function () return execute(first, unpack(arg)) end elseif type(first) == "function" then local info = debug.getinfo(first) @@ -108,7 +169,7 @@ else name = "" end - test_log:write("\n", src, ": ", name, " ", table.concat(arg, " "), "\n") + L(locheader(), name, " ", table.concat(arg, " "), "\n") return function () return first(unpack(arg)) end else error("cmd() called with argument of unknown type " .. type(first), 2) @@ -272,16 +333,6 @@ end end -function P(...) - io.write(unpack(arg)) - io.flush() - logfile:write(unpack(arg)) -end - -function L(...) - test_log:write(unpack(arg)) -end - function run_tests(args) local torun = {} local run_all = true @@ -358,6 +409,7 @@ e = "Could not load driver file " .. driverfile .. " .\n" .. e else r,e = xpcall(driver, debug.traceback) + restore_env() end if r then if wanted_fail then @@ -394,6 +446,7 @@ counts.total = counts.total + 1 end + save_env() if run_all then for i,t in pairs(tests) do if list_only then @@ -416,6 +469,11 @@ end end + if list_only then + logfile:close() + return 0 + end + P("\n") P(string.format("Of %i tests run:\n", counts.total)) P(string.format("\t%i succeeded\n", counts.success)) @@ -432,6 +490,7 @@ logfile:write(dat) end end + logfile:close() if counts.success + counts.skip + counts.xfail == counts.total then return 0 ============================================================ --- testsuite.at d87e4f1992dc6a6a58b5575317b401748758ee6e +++ testsuite.at 4c90fa9da36842d426e761cbd3c5164ec15a157b @@ -566,13 +566,13 @@ m4_include(tests/t_add.at)# m4_include(tests/t_drop.at)# m4_include(tests/t_drop_missing.at)# -m4_include(tests/t_cross.at) -m4_include(tests/t_rename.at) -m4_include(tests/t_renamed.at) -m4_include(tests/t_erename.at) -m4_include(tests/t_cvsimport.at) -m4_include(tests/t_i18n_file.at) -m4_include(tests/t_fmerge.at) +m4_include(tests/t_cross.at)# +m4_include(tests/t_rename.at)# +m4_include(tests/t_renamed.at)# +m4_include(tests/t_erename.at)# +m4_include(tests/t_cvsimport.at)# +m4_include(tests/t_i18n_file.at)# +m4_include(tests/t_fmerge.at)# m4_include(tests/t_netsync.at) m4_include(tests/t_netsync_single.at) m4_include(tests/t_netsync_pubkey.at) ============================================================ --- testsuite.lua 65bc566aa260fd09e30fbfe41566adfb434d9ea1 +++ testsuite.lua af091d9e1c6478f69881ed2290fcc9aa197e2e74 @@ -80,11 +80,10 @@ check(base_revision() == rev) end +ostype = string.sub(get_ostype(), 1, string.find(get_ostype(), " ")-1) + function canonicalize(filename) - local ostype = os.getenv("OSTYPE") - local osenv = os.getenv("OS") - if osenv ~= nil then osenv = string.find(osenv, "[Ww]in") end - if ostype == "msys" or osenv then + if ostype == "Windows" then local f = io.open(filename, "rb") local indat = f:read("*a") f:close() @@ -113,9 +112,16 @@ table.insert(tests, "tests/merging_data_in_unrelated_files") table.insert(tests, "tests/merging_adds_in_unrelated_revisions") table.insert(tests, "tests/merging_data_in_unrelated_revisions") -table.insert(tests, "tests/calculation_of_incorrect_unidiffs") +table.insert(tests, "tests/calculation_of_other_unidiffs") table.insert(tests, "tests/delete_work_file_on_checkout") table.insert(tests, "tests/revert_file_to_base_revision") table.insert(tests, "tests/addition_of_files_and_directories") table.insert(tests, "tests/add_and_then_drop_file_does_nothing") table.insert(tests, "tests/drop_missing_and_unknown_files") +table.insert(tests, "tests/creating_a_bad_criss-cross_merge") +table.insert(tests, "tests/renaming_a_file") +table.insert(tests, "tests/renaming_a_directory") +table.insert(tests, "tests/renaming_and_editing_a_file") +table.insert(tests, "tests/importing_CVS_files") +table.insert(tests, "tests/importing_files_with_non-english_names") +table.insert(tests, "tests/external_unit_test_of_the_line_merger")