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
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:,\
prefix:,program-prefix:,exec-prefix:,lib-suffix:,\
bindir:,libdir:,includedir:,datadir:,\
@ -16,6 +16,7 @@ eval set -- "$OPTS"
PREFIX=${PREFIX:-/usr/local}
ENABLED_PLUGINS=
DISABLED_PLUGINS=
BUILD_TESTS=
DISABLE_FAST_VAPI=
LIB_SUFFIX=
NO_DEBUG=
@ -51,6 +52,7 @@ Configuration:
feature. fast-vapi mode is slower when doing
clean builds, but faster when doing incremental
builds (during development).
--with-tests Also build tests.
Plugin configuration:
--enable-plugin=PLUGIN Enable compilation of plugin PLUGIN.
@ -108,6 +110,7 @@ while true; do
--disable-fast-vapi ) DISABLE_FAST_VAPI=yes; shift ;;
--no-debug ) NO_DEBUG=yes; shift ;;
--fetch-only ) FETCH_ONLY=yes; shift ;;
--with-tests ) BUILD_TESTS=yes; shift ;;
# Autotools paths
--program-prefix ) PREFIX="$2"; shift; shift ;;
--exec-prefix ) EXEC_PREFIX="$2"; shift; shift ;;
@ -238,6 +241,7 @@ cmake -G "$cmake_type" \
-DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DENABLED_PLUGINS="$ENABLED_PLUGINS" \
-DDISABLED_PLUGINS="$DISABLED_PLUGINS" \
-DBUILD_TESTS="$BUILD_TESTS" \
-DVALA_EXECUTABLE="$VALAC" \
-DCMAKE_VALA_FLAGS="$VALACFLAGS" \
-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(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})
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,68 +1,80 @@
using Gee;
namespace Xmpp.Core {
public class NamespaceState {
private HashMap<string, string> uri_to_name = new HashMap<string, string> ();
private HashMap<string, string> name_to_uri = new HashMap<string, string> ();
public string current_ns_uri;
public NamespaceState () {
private NamespaceState parent;
public NamespaceState() {
add_assoc(XMLNS_URI, "xmlns");
add_assoc("http://www.w3.org/XML/1998/namespace", "xml");
current_ns_uri = "http://www.w3.org/XML/1998/namespace";
add_assoc(XML_URI, "xml");
current_ns_uri = XML_URI;
}
public NamespaceState.for_stanza () {
public NamespaceState.for_stanza() {
this();
add_assoc("http://etherx.jabber.org/streams", "stream");
current_ns_uri = "jabber:client";
}
public NamespaceState.copy (NamespaceState old) {
private NamespaceState.copy(NamespaceState old) {
foreach (string key in old.uri_to_name.keys) {
add_assoc(key, old.uri_to_name[key]);
}
set_current(old.current_ns_uri);
}
public NamespaceState.with_assoc (NamespaceState old, string ns_uri, string name) {
private NamespaceState.with_parent(NamespaceState parent) {
this.copy(parent);
this.parent = parent;
}
public NamespaceState.with_assoc(NamespaceState old, string ns_uri, string name) {
this.copy(old);
add_assoc(ns_uri, name);
}
public NamespaceState.with_current (NamespaceState old, string current_ns_uri) {
public NamespaceState.with_current(NamespaceState old, string current_ns_uri) {
this.copy(old);
set_current(current_ns_uri);
}
public void add_assoc (string ns_uri, string name) {
public void add_assoc(string ns_uri, string name) {
name_to_uri[name] = ns_uri;
uri_to_name[ns_uri] = name;
}
public void set_current (string current_ns_uri) {
public void set_current(string current_ns_uri) {
this.current_ns_uri = current_ns_uri;
}
public string find_name (string ns_uri) throws XmlError {
public string find_name(string ns_uri) throws XmlError {
if (uri_to_name.has_key(ns_uri)) {
return uri_to_name[ns_uri];
}
throw new XmlError.NS_DICT_ERROR(@"NS URI $ns_uri not found.");
}
public string find_uri (string name) throws XmlError {
public string find_uri(string name) throws XmlError {
if (name_to_uri.has_key(name)) {
return name_to_uri[name];
}
throw new XmlError.NS_DICT_ERROR(@"NS name $name not found.");
}
public NamespaceState clone() {
return new NamespaceState.copy(this);
public NamespaceState push() {
return new NamespaceState.with_parent(this);
}
public string to_string () {
public NamespaceState pop() {
return parent;
}
public string to_string() {
StringBuilder sb = new StringBuilder ();
sb.append ("NamespaceState{");
foreach (string key in uri_to_name.keys) {
@ -77,4 +89,5 @@ public class NamespaceState {
return sb.str;
}
}
}
}

View File

@ -20,6 +20,13 @@ public class StanzaAttribute : StanzaEntry {
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) {
if (no_ns) {
return fmt.printf(name, (!)val);

View File

@ -300,6 +300,25 @@ public class StanzaNode : StanzaEntry {
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_EMPTY_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 {
NamespaceState my_state = state ?? new NamespaceState.for_stanza();
if (name == "#text") return val == null ? "" : (!)encoded_val;
my_state = my_state.push();
foreach (var xmlns in get_attributes_by_ns_uri (XMLNS_URI)) {
if (xmlns.val == null) continue;
if (xmlns.name == "xmlns") {
my_state = new NamespaceState.with_current(my_state, (!)xmlns.val);
my_state.set_current((!)xmlns.val);
} 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();
@ -391,6 +411,7 @@ public class StanzaNode : StanzaEntry {
}
}
}
my_state = my_state.pop();
return sb.str;
}
}

View File

@ -1,7 +1,9 @@
using Gee;
namespace Xmpp.Core {
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 errordomain XmlError {
@ -85,7 +87,7 @@ public class StanzaReader {
private string read_until_ws() throws XmlError {
var res = new StringBuilder();
var what = peek_single();
while(!is_ws(what)) {
while (!is_ws(what)) {
res.append_c(read_single());
what = peek_single();
}
@ -95,7 +97,7 @@ public class StanzaReader {
private string read_until_char_or_ws(char x, char y = 0) throws XmlError {
var res = new StringBuilder();
var what = peek_single();
while(what != x && what != y && !is_ws(what)) {
while (what != x && what != y && !is_ws(what)) {
res.append_c(read_single());
what = peek_single();
}
@ -105,11 +107,11 @@ public class StanzaReader {
private string read_until_char(char x) throws XmlError {
var res = new StringBuilder();
var what = peek_single();
while(what != x) {
while (what != x) {
res.append_c(read_single());
what = peek_single();
}
return res.str;
return res.str;
}
private StanzaAttribute read_attribute() throws XmlError {
@ -169,7 +171,7 @@ public class StanzaReader {
eof = true;
skip_single();
res.name = read_until_char_or_ws('>');
while(peek_single() != '>') {
while (peek_single() != '>') {
skip_single();
}
skip_single();
@ -184,7 +186,7 @@ public class StanzaReader {
res.attributes.add(read_attribute());
skip_until_non_ws();
}
if (read_single() == '/' || res.pseudo ) {
if (read_single() == '/' || res.pseudo) {
res.has_nodes = false;
skip_single();
} else {
@ -215,8 +217,8 @@ public class StanzaReader {
}
}
public StanzaNode read_stanza_node(NamespaceState? baseNs = null) throws XmlError {
ns_state = baseNs ?? new NamespaceState.for_stanza();
public StanzaNode read_stanza_node() throws XmlError {
ns_state = ns_state.push();
var res = read_node_start();
if (res.has_nodes) {
bool finishNodeSeen = false;
@ -238,8 +240,7 @@ public class StanzaReader {
}
finishNodeSeen = true;
} else {
res.sub_nodes.add(read_stanza_node(ns_state.clone()));
ns_state = baseNs ?? new NamespaceState.for_stanza();
res.sub_nodes.add(read_stanza_node());
}
} else {
res.sub_nodes.add(read_text_node());
@ -247,16 +248,18 @@ public class StanzaReader {
} while (!finishNodeSeen);
if (res.sub_nodes.size == 0) res.has_nodes = false;
}
ns_state = ns_state.pop();
return res;
}
public StanzaNode read_node(NamespaceState? baseNs = null) throws XmlError {
public StanzaNode read_node() throws XmlError {
skip_until_non_ws();
if (peek_single() == '<') {
return read_stanza_node(baseNs ?? new NamespaceState.for_stanza());
return read_stanza_node();
} else {
return read_text_node();
}
}
}
}

View File

@ -11,7 +11,7 @@ public errordomain IOStreamError {
}
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 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 ();
}
}
}