about summary refs log tree commit diff stats
path: root/034address.cc
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2018-06-24 09:16:17 -0700
committerKartik Agaram <vc@akkartik.com>2018-06-24 09:18:20 -0700
commit23d3a02226973f80188e84fa5dcedb14413c5b7f (patch)
tree3c73284cb795e74d78e53b72df470cafca4c70cf /034address.cc
parent377b00b045289a3fa8e88d4b2f129d797c687e2f (diff)
downloadmu-23d3a02226973f80188e84fa5dcedb14413c5b7f.tar.gz
4266 - space for alloc-id in heap allocations
This has taken me almost 6 weeks :(
Diffstat (limited to '034address.cc')
-rw-r--r--034address.cc160
1 files changed, 122 insertions, 38 deletions
diff --git a/034address.cc b/034address.cc
index b97dfb17..6c4bdda4 100644
--- a/034address.cc
+++ b/034address.cc
@@ -18,6 +18,37 @@
 //: write to the payload of an ingredient rather than its value, simply add
 //: the /lookup property to it. Modern computers provide efficient support for
 //: addresses and lookups, making this a realistic feature.
+//:
+//: To create addresses and allocate memory exclusively for their use, use
+//: 'new'. Memory is a finite resource so if the computer can't satisfy your
+//: request, 'new' may return a 0 (null) address.
+//:
+//: Computers these days have lots of memory so in practice we can often
+//: assume we'll never run out. If you start running out however, say in a
+//: long-running program, you'll need to switch mental gears and start
+//: husbanding our memory more carefully. The most important tool to avoid
+//: wasting memory is to 'abandon' an address when you don't need it anymore.
+//: That frees up the memory allocated to it to be reused in future calls to
+//: 'new'.
+
+//: Since memory can be reused multiple times, it can happen that you have a
+//: stale copy to an address that has since been abandoned and reused. Using
+//: the stale address is almost never safe, but it can be very hard to track
+//: down such copies because any errors caused by them may occur even millions
+//: of instructions after the copy or abandon instruction. To help track down
+//: such issues, Mu tracks an 'alloc id' for each allocation it makes. The
+//: first call to 'new' has an alloc id of 1, the second gets 2, and so on.
+//: The alloc id is never reused.
+:(before "End Globals")
+long long Next_alloc_id = 0;
+:(before "End Reset")
+Next_alloc_id = 0;
+
+//: The 'new' instruction records alloc ids both in the memory being allocated
+//: and *also* in the address. The 'abandon' instruction clears alloc ids in
+//: both places as well. Tracking alloc ids in this manner allows us to raise
+//: errors about stale addresses much earlier: 'lookup' operations always
+//: compare alloc ids between the address and its payload.
 
 //: todo: give 'new' a custodian ingredient. Following malloc/free is a temporary hack.
 
@@ -25,29 +56,33 @@
 # call 'new' two times with identical types without modifying the results; you
 # should get back different results
 def main [
-  1:&:num/raw <- new num:type
-  2:&:num/raw <- new num:type
-  3:bool/raw <- equal 1:&:num/raw, 2:&:num/raw
+  10:&:num <- new num:type
+  12:&:num <- new num:type
+  20:bool <- equal 10:&:num, 12:&:num
 ]
-+mem: storing 0 in location 3
++mem: storing 1000 in location 11
++mem: storing 0 in location 20
 
 :(scenario new_array)
 # call 'new' with a second ingredient to allocate an array of some type rather than a single copy
 def main [
-  1:&:@:num/raw <- new num:type, 5
-  2:&:num/raw <- new num:type
-  3:num/raw <- subtract 2:&:num/raw, 1:&:@:num/raw
+  10:&:@:num <- new num:type, 5
+  12:&:num <- new num:type
+  20:num/alloc2, 21:num/alloc1 <- deaddress 10:&:@:num, 12:&:num
+  30:num <- subtract 21:num/alloc2, 20:num/alloc1
 ]
-+run: {1: ("address" "array" "number"), "raw": ()} <- new {num: "type"}, {5: "literal"}
++run: {10: ("address" "array" "number")} <- new {num: "type"}, {5: "literal"}
 +mem: array length is 5
-# don't forget the extra location for array length
-+mem: storing 6 in location 3
+# skip alloc id in allocation
++mem: storing 1000 in location 11
+# don't forget the extra locations for alloc id and array length
++mem: storing 7 in location 30
 
 :(scenario dilated_reagent_with_new)
 def main [
-  1:&:&:num <- new {(& num): type}
+  10:&:&:num <- new {(& num): type}
 ]
-+new: size of '(& num)' is 1
++new: size of '(& num)' is 2
 
 //: 'new' takes a weird 'type' as its first ingredient; don't error on it
 :(before "End Mu Types Initialization")
@@ -135,22 +170,29 @@ def main [
 :(scenario new_discerns_singleton_list_from_atom_container)
 % Hide_errors = true;
 def main [
-  1:&:num/raw <- new {(num): type}  # should be '{num: type}'
+  1:&:num <- new {(num): type}  # should be '{num: type}'
 ]
-+error: main: product of 'new' has incorrect type: '1:&:num/raw <- new {(num): type}'
++error: main: product of 'new' has incorrect type: '1:&:num <- new {(num): type}'
 
 :(scenario new_with_type_abbreviation)
 def main [
-  1:&:num/raw <- new num:type
+  1:&:num <- new num:type
 ]
 $error: 0
 
 :(scenario new_with_type_abbreviation_inside_compound)
 def main [
-  {1: (& & num), raw: ()} <- new {(& num): type}
+  {1: (address address number), raw: ()} <- new {(& num): type}
 ]
 $error: 0
 
+:(scenario equal_result_of_new_with_null)
+def main [
+  1:&:num <- new num:type
+  10:bool <- equal 1:&:num, null
+]
++mem: storing 0 in location 10
+
 //: To implement 'new', a Mu transform turns all 'new' instructions into
 //: 'allocate' instructions that precompute the amount of memory they want to
 //: allocate.
@@ -221,15 +263,18 @@ case ALLOCATE: {
   int result = allocate(size);
   if (SIZE(current_instruction().ingredients) > 1) {
     // initialize array length
-    trace("mem") << "storing " << ingredients.at(1).at(0) << " in location " << result << end();
-    put(Memory, result, ingredients.at(1).at(0));
+    trace("mem") << "storing array length " << ingredients.at(1).at(0) << " in location " << result+/*skip alloc id*/1 << end();
+    put(Memory, result+/*skip alloc id*/1, ingredients.at(1).at(0));
   }
   products.resize(1);
+  products.at(0).push_back(/*alloc id*/0);
   products.at(0).push_back(result);
   break;
 }
 :(code)
 int allocate(int size) {
+  // include space for alloc id
+  ++size;
   trace("mem") << "allocating size " << size << end();
 //?   Total_alloc += size;
 //?   ++Num_alloc;
@@ -289,42 +334,45 @@ def main [
 
 :(scenario new_size)
 def main [
-  11:&:num/raw <- new num:type
-  12:&:num/raw <- new num:type
-  13:num/raw <- subtract 12:&:num/raw, 11:&:num/raw
+  10:&:num <- new num:type
+  12:&:num <- new num:type
+  20:num/alloc1, 21:num/alloc2 <- deaddress 10:&:num, 12:&:num
+  30:num <- subtract 21:num/alloc2, 20:num/alloc1
 ]
-# size of number
-+mem: storing 1 in location 13
+# size of number + alloc id
++mem: storing 2 in location 30
 
 :(scenario new_array_size)
 def main [
-  1:&:@:num/raw <- new num:type, 5
-  2:&:num/raw <- new num:type
-  3:num/raw <- subtract 2:&:num/raw, 1:&:@:num/raw
+  10:&:@:num <- new num:type, 5
+  12:&:num <- new num:type
+  20:num/alloc1, 21:num/alloc2 <- deaddress 10:&:num, 12:&:num
+  30:num <- subtract 21:num/alloc2, 20:num/alloc1
 ]
-# 5 locations for array contents + array length
-+mem: storing 6 in location 3
+# 5 locations for array contents + array length + alloc id
++mem: storing 7 in location 30
 
 :(scenario new_empty_array)
 def main [
-  1:&:@:num/raw <- new num:type, 0
-  2:&:num/raw <- new num:type
-  3:num/raw <- subtract 2:&:num/raw, 1:&:@:num/raw
+  10:&:@:num <- new num:type, 0
+  12:&:num <- new num:type
+  20:num/alloc1, 21:num/alloc2 <- deaddress 10:&:@:num, 12:&:num
+  30:num <- subtract 21:num/alloc2, 20:num/alloc1
 ]
-+run: {1: ("address" "array" "number"), "raw": ()} <- new {num: "type"}, {0: "literal"}
++run: {10: ("address" "array" "number")} <- new {num: "type"}, {0: "literal"}
 +mem: array length is 0
 # one location for array length
-+mem: storing 1 in location 3
++mem: storing 2 in location 30
 
 //: If a routine runs out of its initial allocation, it should allocate more.
 :(scenario new_overflow)
-% Initial_memory_per_routine = 2;  // barely enough room for point allocation below
+% Initial_memory_per_routine = 3;  // barely enough room for point allocation below
 def main [
-  1:&:num/raw <- new num:type
-  2:&:point/raw <- new point:type  # not enough room in initial page
+  10:&:num <- new num:type
+  12:&:point <- new point:type  # not enough room in initial page
 ]
-+new: routine allocated memory from 1000 to 1002
-+new: routine allocated memory from 1002 to 1004
++new: routine allocated memory from 1000 to 1003
++new: routine allocated memory from 1003 to 1006
 
 :(scenario new_without_ingredient)
 % Hide_errors = true;
@@ -332,3 +380,39 @@ def main [
   1:&:num <- new  # missing ingredient
 ]
 +error: main: 'new' requires one or two ingredients, but got '1:&:num <- new'
+
+//: a little helper: convert address to number
+
+:(before "End Primitive Recipe Declarations")
+DEADDRESS,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "deaddress", DEADDRESS);
+:(before "End Primitive Recipe Checks")
+case DEADDRESS: {
+  // primary goal of these checks is to forbid address arithmetic
+  for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
+    if (!is_mu_address(inst.ingredients.at(i))) {
+      raise << maybe(get(Recipe, r).name) << "'deaddress' requires address ingredients, but got '" << inst.ingredients.at(i).original_string << "'\n" << end();
+      goto finish_checking_instruction;
+    }
+  }
+  if (SIZE(inst.products) > SIZE(inst.ingredients)) {
+    raise << maybe(get(Recipe, r).name) << "too many products in '" << to_original_string(inst) << "'\n" << end();
+    break;
+  }
+  for (int i = 0;  i < SIZE(inst.products);  ++i) {
+    if (!is_real_mu_number(inst.products.at(i))) {
+      raise << maybe(get(Recipe, r).name) << "'deaddress' requires number products, but got '" << inst.products.at(i).original_string << "'\n" << end();
+      goto finish_checking_instruction;
+    }
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case DEADDRESS: {
+  products.resize(SIZE(ingredients));
+  for (int i = 0;  i < SIZE(ingredients);  ++i) {
+    products.at(i).push_back(ingredients.at(i).at(/*skip alloc id*/1));
+  }
+  break;
+}
ht .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>

#include "xmpp/xmpp.h"

#include "ui/ui.h"
#include "ui/stub_ui.h"

#include "command/cmd_funcs.h"
#include "config/accounts.h"

#define CMD_CONNECT "/connect"

static void
test_with_connection_status(jabber_conn_status_t status)
{
    will_return(connection_get_status, status);

    expect_cons_show("You are either connected already, or a login is in process.");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, NULL);
    assert_true(result);
}

void
cmd_connect_shows_message_when_disconnecting(void** state)
{
    test_with_connection_status(JABBER_DISCONNECTING);
}

void
cmd_connect_shows_message_when_connecting(void** state)
{
    test_with_connection_status(JABBER_CONNECTING);
}

void
cmd_connect_shows_message_when_connected(void** state)
{
    test_with_connection_status(JABBER_CONNECTED);
}

void
cmd_connect_when_no_account(void** state)
{
    gchar* args[] = { "user@server.org", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(accounts_get_account, name, "user@server.org");
    will_return(accounts_get_account, NULL);

    will_return(ui_ask_password, strdup("password"));

    expect_cons_show("Connecting as user@server.org");

    expect_string(session_connect_with_details, jid, "user@server.org");
    expect_string(session_connect_with_details, passwd, "password");
    expect_value(session_connect_with_details, altdomain, NULL);
    expect_value(session_connect_with_details, port, 0);
    will_return(session_connect_with_details, JABBER_CONNECTING);

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_fail_message(void** state)
{
    gchar* args[] = { "user@server.org", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_any(accounts_get_account, name);
    will_return(accounts_get_account, NULL);

    will_return(ui_ask_password, strdup("password"));

    expect_cons_show("Connecting as user@server.org");

    expect_any(session_connect_with_details, jid);
    expect_any(session_connect_with_details, passwd);
    expect_any(session_connect_with_details, altdomain);
    expect_any(session_connect_with_details, port);
    will_return(session_connect_with_details, JABBER_DISCONNECTED);

    expect_cons_show_error("Connection attempt for user@server.org failed.");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_lowercases_argument_with_no_account(void** state)
{
    gchar* args[] = { "USER@server.ORG", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(accounts_get_account, name, "USER@server.ORG");
    will_return(accounts_get_account, NULL);

    will_return(ui_ask_password, strdup("password"));

    expect_cons_show("Connecting as user@server.org");

    expect_string(session_connect_with_details, jid, "user@server.org");
    expect_string(session_connect_with_details, passwd, "password");
    expect_value(session_connect_with_details, altdomain, NULL);
    expect_value(session_connect_with_details, port, 0);
    will_return(session_connect_with_details, JABBER_CONNECTING);

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_lowercases_argument_with_account(void** state)
{
    gchar* args[] = { "Jabber_org", NULL };
    ProfAccount* account = account_new("Jabber_org", "me@jabber.org", "password", NULL,
                                       TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_any(accounts_get_account, name);
    will_return(accounts_get_account, account);

    expect_cons_show("Connecting with account Jabber_org as me@jabber.org");

    expect_memory(session_connect_with_account, account, account, sizeof(account));
    will_return(session_connect_with_account, JABBER_CONNECTING);

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_asks_password_when_not_in_account(void** state)
{
    gchar* args[] = { "jabber_org", NULL };
    ProfAccount* account = account_new("jabber_org", "me@jabber.org", NULL, NULL,
                                       TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_any(accounts_get_account, name);
    will_return(accounts_get_account, account);

    will_return(ui_ask_password, strdup("password"));

    expect_cons_show("Connecting with account jabber_org as me@jabber.org");

    expect_any(session_connect_with_account, account);
    will_return(session_connect_with_account, JABBER_CONNECTING);

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_usage_when_no_server_value(void** state)
{
    gchar* args[] = { "user@server.org", "server", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(cons_bad_cmd_usage, cmd, CMD_CONNECT);
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_usage_when_server_no_port_value(void** state)
{
    gchar* args[] = { "user@server.org", "server", "aserver", "port", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(cons_bad_cmd_usage, cmd, CMD_CONNECT);
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_usage_when_no_port_value(void** state)
{
    gchar* args[] = { "user@server.org", "port", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(cons_bad_cmd_usage, cmd, CMD_CONNECT);
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_usage_when_port_no_server_value(void** state)
{
    gchar* args[] = { "user@server.org", "port", "5678", "server", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(cons_bad_cmd_usage, cmd, CMD_CONNECT);
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_message_when_port_0(void** state)
{
    gchar* args[] = { "user@server.org", "port", "0", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_cons_show("Value 0 out of range. Must be in 1..65535.");
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_message_when_port_minus1(void** state)
{
    gchar* args[] = { "user@server.org", "port", "-1", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_cons_show("Value -1 out of range. Must be in 1..65535.");
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_message_when_port_65536(void** state)
{
    gchar* args[] = { "user@server.org", "port", "65536", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_cons_show("Value 65536 out of range. Must be in 1..65535.");
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_message_when_port_contains_chars(void** state)
{
    gchar* args[] = { "user@server.org", "port", "52f66", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_cons_show("Could not convert \"52f66\" to a number.");
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_usage_when_server_provided_twice(void** state)
{
    gchar* args[] = { "user@server.org", "server", "server1", "server", "server2", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(cons_bad_cmd_usage, cmd, CMD_CONNECT);
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_usage_when_port_provided_twice(void** state)
{
    gchar* args[] = { "user@server.org", "port", "1111", "port", "1111", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(cons_bad_cmd_usage, cmd, CMD_CONNECT);
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_usage_when_invalid_first_property(void** state)
{
    gchar* args[] = { "user@server.org", "wrong", "server", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(cons_bad_cmd_usage, cmd, CMD_CONNECT);
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_usage_when_invalid_second_property(void** state)
{
    gchar* args[] = { "user@server.org", "server", "aserver", "wrong", "1234", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(cons_bad_cmd_usage, cmd, CMD_CONNECT);
    expect_cons_show("");

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_with_server_when_provided(void** state)
{
    gchar* args[] = { "user@server.org", "server", "aserver", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(accounts_get_account, name, "user@server.org");
    will_return(accounts_get_account, NULL);

    will_return(ui_ask_password, strdup("password"));

    expect_cons_show("Connecting as user@server.org");

    expect_string(session_connect_with_details, jid, "user@server.org");
    expect_string(session_connect_with_details, passwd, "password");
    expect_string(session_connect_with_details, altdomain, "aserver");
    expect_value(session_connect_with_details, port, 0);
    will_return(session_connect_with_details, JABBER_CONNECTING);

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_with_port_when_provided(void** state)
{
    gchar* args[] = { "user@server.org", "port", "5432", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(accounts_get_account, name, "user@server.org");
    will_return(accounts_get_account, NULL);

    will_return(ui_ask_password, strdup("password"));

    expect_cons_show("Connecting as user@server.org");

    expect_string(session_connect_with_details, jid, "user@server.org");
    expect_string(session_connect_with_details, passwd, "password");
    expect_value(session_connect_with_details, altdomain, NULL);
    expect_value(session_connect_with_details, port, 5432);
    will_return(session_connect_with_details, JABBER_CONNECTING);

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_with_server_and_port_when_provided(void** state)
{
    gchar* args[] = { "user@server.org", "port", "5432", "server", "aserver", NULL };

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_string(accounts_get_account, name, "user@server.org");
    will_return(accounts_get_account, NULL);

    will_return(ui_ask_password, strdup("password"));

    expect_cons_show("Connecting as user@server.org");

    expect_string(session_connect_with_details, jid, "user@server.org");
    expect_string(session_connect_with_details, passwd, "password");
    expect_string(session_connect_with_details, altdomain, "aserver");
    expect_value(session_connect_with_details, port, 5432);
    will_return(session_connect_with_details, JABBER_CONNECTING);

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_shows_message_when_connecting_with_account(void** state)
{
    gchar* args[] = { "jabber_org", NULL };
    ProfAccount* account = account_new("jabber_org", "user@jabber.org", "password", NULL,
                                       TRUE, NULL, 0, "laptop", NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_any(accounts_get_account, name);
    will_return(accounts_get_account, account);

    expect_cons_show("Connecting with account jabber_org as user@jabber.org/laptop");

    expect_any(session_connect_with_account, account);
    will_return(session_connect_with_account, JABBER_CONNECTING);

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}

void
cmd_connect_connects_with_account(void** state)
{
    gchar* args[] = { "jabber_org", NULL };
    ProfAccount* account = account_new("jabber_org", "me@jabber.org", "password", NULL,
                                       TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

    will_return(connection_get_status, JABBER_DISCONNECTED);

    expect_any(accounts_get_account, name);
    will_return(accounts_get_account, account);

    expect_cons_show("Connecting with account jabber_org as me@jabber.org");

    expect_memory(session_connect_with_account, account, account, sizeof(account));
    will_return(session_connect_with_account, JABBER_CONNECTING);

    gboolean result = cmd_connect(NULL, CMD_CONNECT, args);
    assert_true(result);
}