~samwhited/xmpp

13486919f78b5c6466d88e57933ab5d13a310145 — Sam Whited 10 months ago d1133b7
internal/integration/mcabber: new package

New package for integration testing against Mcabber.

Signed-off-by: Sam Whited <sam@samwhited.com>
M CHANGELOG.md => CHANGELOG.md +3 -0
@@ 19,6 19,7 @@ All notable changes to this project will be documented in this file.

### Added

- internal/integration/mcabber: [Mcabber] support for integration tests
- oob: implementations of `xmlstream.Marshaler` and `xmlstream.WriterTo` for the
  types `IQ`, `Query`, and Data
- receipts: add `Request` and `Requested` to add receipt requests to messages


@@ 37,6 38,8 @@ All notable changes to this project will be documented in this file.
- xmpp: all sent stanzas are now given randomly generated IDs if no ID was
  provided (not just IQs)

[Mcabber]: https://mcabber.com/


### Changed


M examples/echobot/go.sum => examples/echobot/go.sum +1 -0
@@ 12,6 12,7 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

M examples/im/go.sum => examples/im/go.sum +1 -0
@@ 12,6 12,7 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

M examples/msgrepl/go.sum => examples/msgrepl/go.sum +1 -0
@@ 12,6 12,7 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

M go.mod => go.mod +1 -0
@@ 6,6 6,7 @@ require (
	golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
	golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b
	golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
	golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061
	golang.org/x/text v0.3.2
	mellium.im/sasl v0.2.1
	mellium.im/xmlstream v0.15.2-0.20201219131358-a51cc5cf8151

M go.sum => go.sum +2 -0
@@ 9,6 9,8 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 h1:DQmQoKxQWtyybCtX/3dIuDBcAhFszqq8YiNeS6sNu1c=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

A internal/integration/mcabber/config.go => internal/integration/mcabber/config.go +98 -0
@@ 0,0 1,98 @@
// Copyright 2020 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.

package mcabber

import (
	"os"
	"path/filepath"
	"text/template"

	"mellium.im/xmpp/jid"
)

// Config contains options that can be written to the config file.
type Config struct {
	JID      jid.JID
	Password string
	FIFO     *os.File
	Port     string
}

const cfgBase = `set jid = {{.JID}}
set vi_mode=1
set password = {{.Password}}
set server = localhost
set port = {{ .Port }}
set resource = mcabbertest
set disable_random_resource = 0
set tls = 1
set ssl_ignore_checks = 1
set pinginterval = 0
set spell_enable = 0
set disable_chatstates = 1
set logging = 1
set load_logs = 0
set logging_dir = {{ .ConfigDir }}
set log_muc_conf = 1
set statefile = {{ filepathJoin .ConfigDir "mcabber.state" }}


# Modules
# If mcabber is built with modules support, you can specify the path
# to the directory where your modules reside. Though, default compiled-in
# value should be appropriate.
#set modules_dir = /usr/lib/mcabber/

set beep_on_message = 0
set event_log_files = 1
set event_log_dir = {{ .ConfigDir }}

# Internal hooks
# You can ask mcabber to execute an internal command when a special event
# occurs (for example when it connects to the server).
#
# 'hook-post-connect' is executed when mcabber has connected to the server
# and the roster has been received.
#set hook-post-connect = status dnd
#
# 'hook-pre-disconnect' is executed just before mcabber disconnects from
# the server.
#set hook-pre-disconnect = say_to foo@bar Goodbye!

# FIFO
# mcabber can create a FIFO named pipe and listen to this pipe for commands.
# Don't forget to load the FIFO module if you plan to use this feature!
# Default: disabled.
# Set 'fifo_hide_commands' to 1 if you don't want to see the FIFO commands
# in the log window (they will still be written to the tracelog file).
# When FIFO  is configured, you can turn it off and on in real time with
# the 'fifo_ignore' option (default: 0).  When set to 1, the FIFO input is
# still read but it is discarded.
set fifo_name = {{ .FIFO.Name }}
module load fifo


# Traces logging
# If you want advanced traces, please specify a file and a level here.
# There are currently 4 tracelog levels:
#  lvl 1: most events of the log window are written to the file
#  lvl 2: Loudmouth verbose logging
#  lvl 3: debug logging (XML, etc.)
#  lvl 4: noisy debug logging (Loudmouth parser...)
# Default is level 0, no trace logging
set tracelog_level = 4
set tracelog_file = {{ filepathJoin .ConfigDir "trace.log" }}
set autoaway = 0
set muc_print_status = 3
set muc_print_jid = 2
set log_display_presence = 1
set show_status_in_buffer = 2
set log_display_sender = 1
set caps_directory = "{{ .ConfigDir }}"
`

var cfgTmpl = template.Must(template.New("cfg").Funcs(template.FuncMap{
	"filepathJoin": filepath.Join,
}).Parse(cfgBase))

A internal/integration/mcabber/mcabber.go => internal/integration/mcabber/mcabber.go +99 -0
@@ 0,0 1,99 @@
// Copyright 2020 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.

// Package mcabber facilitates integration testing against Mcabber.
package mcabber // import "mellium.im/xmpp/internal/integration/mcabber"

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"testing"

	"golang.org/x/sys/unix"

	"mellium.im/xmpp/internal/integration"
	"mellium.im/xmpp/jid"
)

const (
	cfgFileName = "mcabberrc"
	cmdName     = "mcabber"
	configFlag  = "-f"
	controlFIFO = "command.socket"
)

// Send transmits the given command over the control pipe.
func Send(cmd *integration.Cmd, s string) error {
	cfg := getConfig(cmd)
	_, err := fmt.Fprintln(cfg.FIFO, s)
	return err
}

// Ping sends an XMPP ping through Mcabber.
func Ping(cmd *integration.Cmd, to jid.JID) error {
	return Send(cmd, fmt.Sprintf("request ping %s", to))
}

// ConfigFile is an option that can be used to write a temporary config file.
// This will overwrite the existing config file and make most of the other
// options in this package noops.
// This option only exists for the rare occasion that you need complete control
// over the config file.
func ConfigFile(cfg Config) integration.Option {
	return func(cmd *integration.Cmd) error {
		if cfg.FIFO == nil {
			fifoPath := filepath.Join(cmd.ConfigDir(), controlFIFO)
			err := unix.Mkfifo(fifoPath, 0660)
			if err != nil {
				return err
			}
			cfg.FIFO, err = os.OpenFile(fifoPath, os.O_RDWR, os.ModeNamedPipe)
			if err != nil {
				return err
			}
		}

		cmd.Config = cfg
		err := integration.TempFile(cfgFileName, func(cmd *integration.Cmd, w io.Writer) error {
			return cfgTmpl.Execute(w, struct {
				Config
				ConfigDir string
			}{
				Config:    cfg,
				ConfigDir: cmd.ConfigDir(),
			})
		})(cmd)
		if err != nil {
			return err
		}
		cfgFilePath := filepath.Join(cmd.ConfigDir(), cfgFileName)
		return integration.Args(configFlag, cfgFilePath)(cmd)
	}
}

func getConfig(cmd *integration.Cmd) Config {
	if cmd.Config == nil {
		cmd.Config = Config{}
	}
	return cmd.Config.(Config)
}

func defaultConfig(cmd *integration.Cmd) error {
	return integration.Shutdown(func(cmd *integration.Cmd) error {
		return Send(cmd, "quit")
	})(cmd)
}

// Test starts a Mcabber instance and returns a function that runs subtests
// using t.Run.
// Multiple calls to the returned function will result in uniquely named
// subtests.
// When all subtests have completed, the daemon is stopped.
func Test(ctx context.Context, t *testing.T, opts ...integration.Option) integration.SubtestRunner {
	opts = append(opts, defaultConfig)
	return integration.Test(ctx, cmdName, t, opts...)
}