xmpp-vala: improve namespace handling, add some tests

This commit is contained in:
Marvin W 2017-05-13 17:43:51 +02:00
parent dd88db7556
commit 6904bda756
No known key found for this signature in database
GPG key ID: 072E9235DB996F2A
10 changed files with 375 additions and 31 deletions

6
configure vendored
View file

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
OPTS=`getopt -o "h" --long \ OPTS=`getopt -o "h" --long \
help,fetch-only,no-debug,disable-fast-vapi,\ help,fetch-only,no-debug,disable-fast-vapi,with-tests,\
enable-plugin:,disable-plugin:,\ enable-plugin:,disable-plugin:,\
prefix:,program-prefix:,exec-prefix:,lib-suffix:,\ prefix:,program-prefix:,exec-prefix:,lib-suffix:,\
bindir:,libdir:,includedir:,datadir:,\ bindir:,libdir:,includedir:,datadir:,\
@ -16,6 +16,7 @@ eval set -- "$OPTS"
PREFIX=${PREFIX:-/usr/local} PREFIX=${PREFIX:-/usr/local}
ENABLED_PLUGINS= ENABLED_PLUGINS=
DISABLED_PLUGINS= DISABLED_PLUGINS=
BUILD_TESTS=
DISABLE_FAST_VAPI= DISABLE_FAST_VAPI=
LIB_SUFFIX= LIB_SUFFIX=
NO_DEBUG= NO_DEBUG=
@ -51,6 +52,7 @@ Configuration:
feature. fast-vapi mode is slower when doing feature. fast-vapi mode is slower when doing
clean builds, but faster when doing incremental clean builds, but faster when doing incremental
builds (during development). builds (during development).
--with-tests Also build tests.
Plugin configuration: Plugin configuration:
--enable-plugin=PLUGIN Enable compilation of plugin PLUGIN. --enable-plugin=PLUGIN Enable compilation of plugin PLUGIN.
@ -108,6 +110,7 @@ while true; do
--disable-fast-vapi ) DISABLE_FAST_VAPI=yes; shift ;; --disable-fast-vapi ) DISABLE_FAST_VAPI=yes; shift ;;
--no-debug ) NO_DEBUG=yes; shift ;; --no-debug ) NO_DEBUG=yes; shift ;;
--fetch-only ) FETCH_ONLY=yes; shift ;; --fetch-only ) FETCH_ONLY=yes; shift ;;
--with-tests ) BUILD_TESTS=yes; shift ;;
# Autotools paths # Autotools paths
--program-prefix ) PREFIX="$2"; shift; shift ;; --program-prefix ) PREFIX="$2"; shift; shift ;;
--exec-prefix ) EXEC_PREFIX="$2"; shift; shift ;; --exec-prefix ) EXEC_PREFIX="$2"; shift; shift ;;
@ -238,6 +241,7 @@ cmake -G "$cmake_type" \
-DCMAKE_INSTALL_PREFIX="$PREFIX" \ -DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DENABLED_PLUGINS="$ENABLED_PLUGINS" \ -DENABLED_PLUGINS="$ENABLED_PLUGINS" \
-DDISABLED_PLUGINS="$DISABLED_PLUGINS" \ -DDISABLED_PLUGINS="$DISABLED_PLUGINS" \
-DBUILD_TESTS="$BUILD_TESTS" \
-DVALA_EXECUTABLE="$VALAC" \ -DVALA_EXECUTABLE="$VALAC" \
-DCMAKE_VALA_FLAGS="$VALACFLAGS" \ -DCMAKE_VALA_FLAGS="$VALACFLAGS" \
-DDISABLE_FAST_VAPI="$DISABLE_FAST_VAPI" \ -DDISABLE_FAST_VAPI="$DISABLE_FAST_VAPI" \

View file

@ -85,3 +85,21 @@ set_target_properties(xmpp-vala PROPERTIES VERSION 0.1 SOVERSION 0)
install(TARGETS xmpp-vala ${TARGET_INSTALL}) install(TARGETS xmpp-vala ${TARGET_INSTALL})
install(FILES ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.deps DESTINATION ${VAPI_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.deps DESTINATION ${VAPI_INSTALL_DIR})
install(FILES ${CMAKE_BINARY_DIR}/exports/xmpp-vala.h DESTINATION ${INCLUDE_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/xmpp-vala.h DESTINATION ${INCLUDE_INSTALL_DIR})
if(BUILD_TESTS)
vala_precompile(ENGINE_TEST_VALA_C
SOURCES
"tests/common.vala"
"tests/testcase.vala"
"tests/stanza.vala"
CUSTOM_VAPIS
${CMAKE_BINARY_DIR}/exports/xmpp-vala_internal.vapi
PACKAGES
${ENGINE_PACKAGES}
)
add_definitions(${VALA_CFLAGS})
add_executable(xmpp-vala-test ${ENGINE_TEST_VALA_C})
target_link_libraries(xmpp-vala-test xmpp-vala ${SIGNAL_PROTOCOL_PACKAGES})
endif(BUILD_TESTS)

View file

@ -1,15 +1,18 @@
using Gee; using Gee;
namespace Xmpp.Core { namespace Xmpp.Core {
public class NamespaceState { public class NamespaceState {
private HashMap<string, string> uri_to_name = new HashMap<string, string> (); private HashMap<string, string> uri_to_name = new HashMap<string, string> ();
private HashMap<string, string> name_to_uri = new HashMap<string, string> (); private HashMap<string, string> name_to_uri = new HashMap<string, string> ();
public string current_ns_uri; public string current_ns_uri;
private NamespaceState parent;
public NamespaceState() { public NamespaceState() {
add_assoc(XMLNS_URI, "xmlns"); add_assoc(XMLNS_URI, "xmlns");
add_assoc("http://www.w3.org/XML/1998/namespace", "xml"); add_assoc(XML_URI, "xml");
current_ns_uri = "http://www.w3.org/XML/1998/namespace"; current_ns_uri = XML_URI;
} }
public NamespaceState.for_stanza() { public NamespaceState.for_stanza() {
@ -18,13 +21,18 @@ public class NamespaceState {
current_ns_uri = "jabber:client"; current_ns_uri = "jabber:client";
} }
public NamespaceState.copy (NamespaceState old) { private NamespaceState.copy(NamespaceState old) {
foreach (string key in old.uri_to_name.keys) { foreach (string key in old.uri_to_name.keys) {
add_assoc(key, old.uri_to_name[key]); add_assoc(key, old.uri_to_name[key]);
} }
set_current(old.current_ns_uri); set_current(old.current_ns_uri);
} }
private NamespaceState.with_parent(NamespaceState parent) {
this.copy(parent);
this.parent = parent;
}
public NamespaceState.with_assoc(NamespaceState old, string ns_uri, string name) { public NamespaceState.with_assoc(NamespaceState old, string ns_uri, string name) {
this.copy(old); this.copy(old);
add_assoc(ns_uri, name); add_assoc(ns_uri, name);
@ -58,8 +66,12 @@ public class NamespaceState {
throw new XmlError.NS_DICT_ERROR(@"NS name $name not found."); throw new XmlError.NS_DICT_ERROR(@"NS name $name not found.");
} }
public NamespaceState clone() { public NamespaceState push() {
return new NamespaceState.copy(this); return new NamespaceState.with_parent(this);
}
public NamespaceState pop() {
return parent;
} }
public string to_string() { public string to_string() {
@ -77,4 +89,5 @@ public class NamespaceState {
return sb.str; return sb.str;
} }
} }
} }

View file

@ -20,6 +20,13 @@ public class StanzaAttribute : StanzaEntry {
this.val = val; this.val = val;
} }
public bool equals(StanzaAttribute other) {
if (other.ns_uri != ns_uri) return false;
if (other.name != name) return false;
if (other.val != val) return false;
return true;
}
internal string printf(string fmt, bool no_ns = false, string? ns_name = null) { internal string printf(string fmt, bool no_ns = false, string? ns_name = null) {
if (no_ns) { if (no_ns) {
return fmt.printf(name, (!)val); return fmt.printf(name, (!)val);

View file

@ -300,6 +300,25 @@ public class StanzaNode : StanzaEntry {
return this; return this;
} }
public bool equals(StanzaNode other) {
if (other.name != name) return false;
if (other.val != val) return false;
if (name == "#text") return true;
if (other.ns_uri != ns_uri) return false;
if (other.sub_nodes.size != sub_nodes.size) return false;
for (int i = 0; i < sub_nodes.size; i++) {
if (!other.sub_nodes[i].equals(sub_nodes[i])) return false;
}
if (other.attributes.size != attributes.size) return false;
for (int i = 0; i < attributes.size; i++) {
if (!other.attributes[i].equals(attributes[i])) return false;
}
return true;
}
private const string TAG_START_BEGIN_FORMAT = "%s<{%s}:%s"; private const string TAG_START_BEGIN_FORMAT = "%s<{%s}:%s";
private const string TAG_START_EMPTY_END = " />\n"; private const string TAG_START_EMPTY_END = " />\n";
private const string TAG_START_CONTENT_END = ">\n"; private const string TAG_START_CONTENT_END = ">\n";
@ -358,12 +377,13 @@ public class StanzaNode : StanzaEntry {
public string to_xml(NamespaceState? state = null) throws XmlError { public string to_xml(NamespaceState? state = null) throws XmlError {
NamespaceState my_state = state ?? new NamespaceState.for_stanza(); NamespaceState my_state = state ?? new NamespaceState.for_stanza();
if (name == "#text") return val == null ? "" : (!)encoded_val; if (name == "#text") return val == null ? "" : (!)encoded_val;
my_state = my_state.push();
foreach (var xmlns in get_attributes_by_ns_uri (XMLNS_URI)) { foreach (var xmlns in get_attributes_by_ns_uri (XMLNS_URI)) {
if (xmlns.val == null) continue; if (xmlns.val == null) continue;
if (xmlns.name == "xmlns") { if (xmlns.name == "xmlns") {
my_state = new NamespaceState.with_current(my_state, (!)xmlns.val); my_state.set_current((!)xmlns.val);
} else { } else {
my_state = new NamespaceState.with_assoc(my_state, (!)xmlns.val, xmlns.name); my_state.add_assoc((!)xmlns.val, xmlns.name);
} }
} }
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -391,6 +411,7 @@ public class StanzaNode : StanzaEntry {
} }
} }
} }
my_state = my_state.pop();
return sb.str; return sb.str;
} }
} }

View file

@ -1,7 +1,9 @@
using Gee; using Gee;
namespace Xmpp.Core { namespace Xmpp.Core {
public const string XMLNS_URI = "http://www.w3.org/2000/xmlns/"; public const string XMLNS_URI = "http://www.w3.org/2000/xmlns/";
public const string XML_URI = "http://www.w3.org/XML/1998/namespace";
public const string JABBER_URI = "jabber:client"; public const string JABBER_URI = "jabber:client";
public errordomain XmlError { public errordomain XmlError {
@ -215,8 +217,8 @@ public class StanzaReader {
} }
} }
public StanzaNode read_stanza_node(NamespaceState? baseNs = null) throws XmlError { public StanzaNode read_stanza_node() throws XmlError {
ns_state = baseNs ?? new NamespaceState.for_stanza(); ns_state = ns_state.push();
var res = read_node_start(); var res = read_node_start();
if (res.has_nodes) { if (res.has_nodes) {
bool finishNodeSeen = false; bool finishNodeSeen = false;
@ -238,8 +240,7 @@ public class StanzaReader {
} }
finishNodeSeen = true; finishNodeSeen = true;
} else { } else {
res.sub_nodes.add(read_stanza_node(ns_state.clone())); res.sub_nodes.add(read_stanza_node());
ns_state = baseNs ?? new NamespaceState.for_stanza();
} }
} else { } else {
res.sub_nodes.add(read_text_node()); res.sub_nodes.add(read_text_node());
@ -247,16 +248,18 @@ public class StanzaReader {
} while (!finishNodeSeen); } while (!finishNodeSeen);
if (res.sub_nodes.size == 0) res.has_nodes = false; if (res.sub_nodes.size == 0) res.has_nodes = false;
} }
ns_state = ns_state.pop();
return res; return res;
} }
public StanzaNode read_node(NamespaceState? baseNs = null) throws XmlError { public StanzaNode read_node() throws XmlError {
skip_until_non_ws(); skip_until_non_ws();
if (peek_single() == '<') { if (peek_single() == '<') {
return read_stanza_node(baseNs ?? new NamespaceState.for_stanza()); return read_stanza_node();
} else { } else {
return read_text_node(); return read_text_node();
} }
} }
} }
} }

View file

@ -11,7 +11,7 @@ public errordomain IOStreamError {
} }
public class XmppStream { public class XmppStream {
private static string NS_URI = "http://etherx.jabber.org/streams"; public const string NS_URI = "http://etherx.jabber.org/streams";
public string remote_name; public string remote_name;
public XmppLog log = new XmppLog(); public XmppLog log = new XmppLog();

View file

@ -0,0 +1,93 @@
namespace Xmpp.Test {
int main(string[] args) {
GLib.Test.init(ref args);
GLib.Test.set_nonfatal_assertions();
TestSuite.get_root().add_suite(new Xmpp.Test.StanzaTest().get_suite());
return GLib.Test.run();
}
bool fail_if(bool exp, string? reason = null) {
if (exp) {
if (reason != null) GLib.Test.message(reason);
GLib.Test.fail();
return true;
}
return false;
}
void fail_if_reached(string? reason = null) {
fail_if(true, reason);
}
delegate void ErrorFunc() throws Error;
void fail_if_not_error_code(ErrorFunc func, int expectedCode, string? reason = null) {
try {
func();
fail_if_reached(@"$(reason + ": " ?? "")no error thrown");
} catch (Error e) {
fail_if_not_eq_int(e.code, expectedCode, @"$(reason + ": " ?? "")catched unexpected error");
}
}
bool fail_if_not(bool exp, string? reason = null) {
return fail_if(!exp, reason);
}
bool fail_if_eq_int(int left, int right, string? reason = null) {
return fail_if(left == right, @"$(reason + ": " ?? "")$left == $right");
}
bool fail_if_not_eq_node(Core.StanzaNode left, Core.StanzaNode right, string? reason = null) {
if (fail_if_not_eq_str(left.name, right.name, @"$(reason + ": " ?? "")name mismatch")) return true;
if (fail_if_not_eq_str(left.val, right.val, @"$(reason + ": " ?? "")val mismatch")) return true;
if (left.name == "#text") return false;
if (fail_if_not_eq_str(left.ns_uri, right.ns_uri, @"$(reason + ": " ?? "")ns_uri mismatch")) return true;
if (fail_if_not_eq_int(left.sub_nodes.size, right.sub_nodes.size, @"$(reason + ": " ?? "")sub node count mismatch")) return true;
if (fail_if_not_eq_int(left.attributes.size, right.attributes.size, @"$(reason + ": " ?? "")attributes count mismatch")) return true;
for (var i = 0; i < left.sub_nodes.size; i++) {
if (fail_if_not_eq_node(left.sub_nodes[i], right.sub_nodes[i], @"$(reason + ": " ?? "")$(i+1)th subnode mismatch")) return true;
}
for (var i = 0; i < left.attributes.size; i++) {
if (fail_if_not_eq_attr(left.attributes[i], right.attributes[i], @"$(reason + ": " ?? "")$(i+1)th attribute mismatch")) return true;
}
return false;
}
bool fail_if_not_eq_attr(Core.StanzaAttribute left, Core.StanzaAttribute right, string? reason = null) {
if (fail_if_not_eq_str(left.name, right.name, @"$(reason + ": " ?? "")name mismatch")) return true;
if (fail_if_not_eq_str(left.val, right.val, @"$(reason + ": " ?? "")val mismatch")) return true;
if (fail_if_not_eq_str(left.ns_uri, right.ns_uri, @"$(reason + ": " ?? "")ns_uri mismatch")) return true;
return false;
}
bool fail_if_not_eq_int(int left, int right, string? reason = null) {
return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right");
}
bool fail_if_not_eq_str(string? left, string? right, string? reason = null) {
bool nullcheck = (left == null || right == null) && (left != null && right != null);
if (left == null) left = "(null)";
if (right == null) right = "(null)";
return fail_if_not(!nullcheck && left == right, @"$(reason + ": " ?? "")$left != $right");
}
bool fail_if_not_eq_uint8_arr(uint8[] left, uint8[] right, string? reason = null) {
if (fail_if_not_eq_int(left.length, right.length, @"$(reason + ": " ?? "")array length not equal")) return true;
return fail_if_not_eq_str(Base64.encode(left), Base64.encode(right), reason);
}
bool fail_if_not_zero_int(int zero, string? reason = null) {
return fail_if_not_eq_int(zero, 0, reason);
}
bool fail_if_zero_int(int zero, string? reason = null) {
return fail_if_eq_int(zero, 0, reason);
}
bool fail_if_null(void* what, string? reason = null) {
return fail_if(what == null || (size_t)what == 0, reason);
}
}

105
xmpp-vala/tests/stanza.vala Normal file
View file

@ -0,0 +1,105 @@
using Xmpp.Core;
namespace Xmpp.Test {
class StanzaTest : Gee.TestCase {
public StanzaTest() {
base("Stanza");
add_test("node_one", test_node_one);
add_test("typical_stream", test_typical_stream);
add_test("ack_stream", test_ack_stream);
}
private void test_node_one() {
var node1 = new StanzaNode.build("test", "ns1_uri")
.add_self_xmlns()
.put_attribute("ns2", "ns2_uri", XMLNS_URI)
.put_attribute("bla", "blub")
.put_node(new StanzaNode.build("testaa", "ns2_uri")
.put_attribute("ns3", "ns3_uri", XMLNS_URI))
.put_node(new StanzaNode.build("testbb", "ns3_uri")
.add_self_xmlns());
var xml1 = node1.to_xml();
var node2 = new StanzaReader.for_string(xml1).read_node();
fail_if_not(node1.equals(node2));
fail_if_not_eq_str(node1.to_string(), node2.to_string());
}
private void test_typical_stream() {
var stream = """
<?xml version='1.0' encoding='UTF-8'?>
<stream:stream
to='example.com'
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'
version='1.0'>
<message from='laurence@example.net/churchyard'
to='juliet@example.com'
xml:lang='en'>
<body>I'll send a friar with speed, to Mantua, with my letters to thy lord.</body>
</message>
</stream:stream>
""";
var root_node_cmp = new StanzaNode.build("stream", "http://etherx.jabber.org/streams")
.put_attribute("to", "example.com")
.put_attribute("xmlns", "jabber:client")
.put_attribute("stream", "http://etherx.jabber.org/streams", XMLNS_URI)
.put_attribute("version", "1.0");
var node_cmp = new StanzaNode.build("message")
.put_attribute("from", "laurence@example.net/churchyard")
.put_attribute("to", "juliet@example.com")
.put_attribute("lang", "en", XML_URI)
.put_node(new StanzaNode.build("body")
.put_node(new StanzaNode.text("I'll send a friar with speed, to Mantua, with my letters to thy lord.")));
var reader = new StanzaReader.for_string(stream);
fail_if_not_eq_node(root_node_cmp, reader.read_root_node());
fail_if_not_eq_node(node_cmp, reader.read_node());
reader.read_node();
fail_if_not_error_code(() => reader.read_node(), 3, "end of stream should be reached");
}
private void test_ack_stream() {
var stream = """
<?xml version='1.0' encoding='UTF-8'?>
<stream:stream
to='example.com'
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'
xmlns:ack='http://jabber.org/protocol/ack'
version='1.0'>
<stream:features>
<ack:ack/>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
<required/>
</bind>
</stream:features>
<ack:r/>
</stream:stream>
""";
var root_node_cmp = new StanzaNode.build("stream", "http://etherx.jabber.org/streams")
.put_attribute("to", "example.com")
.put_attribute("xmlns", "jabber:client")
.put_attribute("stream", "http://etherx.jabber.org/streams", XMLNS_URI)
.put_attribute("ack", "http://jabber.org/protocol/ack", XMLNS_URI)
.put_attribute("version", "1.0");
var node_cmp = new StanzaNode.build("features", XmppStream.NS_URI)
.put_node(new StanzaNode.build("ack", "http://jabber.org/protocol/ack"))
.put_node(new StanzaNode.build("bind", "urn:ietf:params:xml:ns:xmpp-bind")
.add_self_xmlns()
.put_node(new StanzaNode.build("required", "urn:ietf:params:xml:ns:xmpp-bind")));
var node2_cmp = new StanzaNode.build("r", "http://jabber.org/protocol/ack");
var reader = new StanzaReader.for_string(stream);
fail_if_not_eq_node(root_node_cmp, reader.read_root_node());
fail_if_not_eq_node(node_cmp, reader.read_node());
fail_if_not_eq_node(node2_cmp, reader.read_node());
reader.read_node();
fail_if_not_error_code(() => reader.read_node(), 3, "end of stream should be reached");
}
}
}

View file

@ -0,0 +1,80 @@
/* testcase.vala
*
* Copyright (C) 2009 Julien Peeters
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author:
* Julien Peeters <contact@julienpeeters.fr>
*/
public abstract class Gee.TestCase : Object {
private GLib.TestSuite suite;
private Adaptor[] adaptors = new Adaptor[0];
public delegate void TestMethod ();
public TestCase (string name) {
this.suite = new GLib.TestSuite (name);
}
public void add_test (string name, owned TestMethod test) {
var adaptor = new Adaptor (name, (owned)test, this);
this.adaptors += adaptor;
this.suite.add (new GLib.TestCase (adaptor.name,
adaptor.set_up,
adaptor.run,
adaptor.tear_down ));
}
public virtual void set_up () {
}
public virtual void tear_down () {
}
public GLib.TestSuite get_suite () {
return this.suite;
}
private class Adaptor {
[CCode (notify = false)]
public string name { get; private set; }
private TestMethod test;
private TestCase test_case;
public Adaptor (string name,
owned TestMethod test,
TestCase test_case) {
this.name = name;
this.test = (owned)test;
this.test_case = test_case;
}
public void set_up (void* fixture) {
this.test_case.set_up ();
}
public void run (void* fixture) {
this.test ();
}
public void tear_down (void* fixture) {
this.test_case.tear_down ();
}
}
}