Implemented integration tests for the control pad (#476)

* Added two abstract methods setSharedPreferences and
     configureHostInfo to allow test classes extending
     AbstractTestClass to set preferences or change the host info.
   * Implemented a mock event server to test event server commands
   * Implemented a handler to handle JSON input events over HTTP
This commit is contained in:
Martijn Brekhof 2017-10-24 20:36:38 +02:00 committed by Synced Synapse
parent 8f6541e956
commit bdefeafc3b
17 changed files with 973 additions and 16 deletions

View File

@ -0,0 +1,46 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.testhelpers;
import org.xbmc.kore.testutils.tcpserver.handlers.InputHandler;
import static junit.framework.Assert.assertTrue;
import static org.xbmc.kore.tests.ui.AbstractTestClass.getInputHandler;
public class TestUtils {
/**
* Tests if the event received at the server matches the given
* method name and action
* @param methodName name of the method that should be received serverside.
* @param executeAction name of the action that should be received serverside. May be null if the input does not specify an action.
*/
public static void testHTTPEvent(String methodName, String executeAction) {
InputHandler inputHandler = getInputHandler();
assertTrue(inputHandler != null);
String methodNameReceived = inputHandler.getMethodName();
assertTrue(methodNameReceived != null);
assertTrue(methodNameReceived.contentEquals(methodName));
if (executeAction != null) {
String actionReceived = inputHandler.getAction();
assertTrue(actionReceived != null);
assertTrue(actionReceived.contentEquals(executeAction));
}
}
}

View File

@ -31,6 +31,7 @@ import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.provider.MediaProvider;
import org.xbmc.kore.ui.sections.hosts.HostFragmentManualConfiguration;
import org.xbmc.kore.utils.LogUtils;
import java.lang.reflect.Method;
@ -88,7 +89,14 @@ public class Utils {
public static void setLearnedAboutDrawerPreference(Context context, boolean learned) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(PREF_USER_LEARNED_DRAWER, true);
editor.putBoolean(PREF_USER_LEARNED_DRAWER, learned);
editor.commit();
}
public static void setUseEventServerPreference(Context context, boolean use) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(HostFragmentManualConfiguration.HOST_USE_EVENT_SERVER, use);
editor.commit();
}

View File

@ -19,7 +19,6 @@ package org.xbmc.kore.tests.ui;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
@ -33,27 +32,42 @@ import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.HostConnection;
import org.xbmc.kore.provider.MediaProvider;
import org.xbmc.kore.testhelpers.LoaderIdlingResource;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.testutils.Database;
import org.xbmc.kore.testutils.tcpserver.MockTcpServer;
import org.xbmc.kore.testutils.tcpserver.handlers.AddonsHandler;
import org.xbmc.kore.testutils.tcpserver.handlers.ApplicationHandler;
import org.xbmc.kore.testutils.tcpserver.handlers.InputHandler;
import org.xbmc.kore.testutils.tcpserver.handlers.JSONConnectionHandlerManager;
import org.xbmc.kore.testutils.tcpserver.handlers.JSONRPCHandler;
import org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler;
import org.xbmc.kore.ui.sections.hosts.HostFragmentManualConfiguration;
import org.xbmc.kore.utils.LogUtils;
import java.io.IOException;
@RunWith(AndroidJUnit4.class)
@Ignore
abstract public class AbstractTestClass<T extends AppCompatActivity> {
private static final String TAG = LogUtils.makeLogTag(AbstractTestClass.class);
abstract protected ActivityTestRule<T> getActivityTestRule();
/**
* Method that can be used to change the shared preferences.
* This will be called before each test after clearing the settings
* in {@link #setUp()}
*/
abstract protected void setSharedPreferences(Context context);
/**
* Called from {@link #setUp()} right after HostInfo has been created.
* @param hostInfo created HostInfo used by the activity under test
*/
abstract protected void configureHostInfo(HostInfo hostInfo);
private LoaderIdlingResource loaderIdlingResource;
private ActivityTestRule<T> activityTestRule;
private static MockTcpServer server;
@ -61,6 +75,7 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
private AddonsHandler addonsHandler;
private static PlayerHandler playerHandler;
private static ApplicationHandler applicationHandler;
private static InputHandler inputHandler;
private HostInfo hostInfo;
@ -68,9 +83,11 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
public static void setupMockTCPServer() throws Throwable {
playerHandler = new PlayerHandler();
applicationHandler = new ApplicationHandler();
inputHandler = new InputHandler();
manager = new JSONConnectionHandlerManager();
manager.addHandler(playerHandler);
manager.addHandler(applicationHandler);
manager.addHandler(inputHandler);
manager.addHandler(new JSONRPCHandler());
server = new MockTcpServer(manager);
server.start();
@ -81,8 +98,15 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
activityTestRule = getActivityTestRule();
// final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
final Context context = activityTestRule.getActivity();
if (context == null)
throw new RuntimeException("Could not get context. Maybe activity failed to start?");
Utils.clearSharedPreferences(context);
//Prevent drawer from opening when we start a new activity
Utils.setLearnedAboutDrawerPreference(context, true);
//Allow each test to change the shared preferences
setSharedPreferences(context);
//Note: as the activity is not yet available in @BeforeClass we need
// to add the handler here
@ -91,14 +115,14 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
manager.addHandler(addonsHandler);
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean useEventServer = prefs.getBoolean(HostFragmentManualConfiguration.HOST_USE_EVENT_SERVER, false);
hostInfo = Database.addHost(context, server.getHostName(),
HostConnection.PROTOCOL_TCP, HostInfo.DEFAULT_HTTP_PORT,
server.getPort());
Utils.clearSharedPreferences(context);
//Prevent drawer from opening when we start a new activity
Utils.setLearnedAboutDrawerPreference(context, true);
server.getPort(), useEventServer);
//Allow each test to change the host info
configureHostInfo(hostInfo);
loaderIdlingResource = new LoaderIdlingResource(activityTestRule.getActivity().getSupportLoaderManager());
Espresso.registerIdlingResources(loaderIdlingResource);
@ -111,7 +135,7 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
Utils.switchHost(context, activityTestRule.getActivity(), hostInfo);
//Relaunch the activity for the changes (Host selection and database fill) to take effect
//Relaunch the activity for the changes (Host selection, preference changes, and database fill) to take effect
activityTestRule.launchActivity(new Intent());
}
@ -147,4 +171,8 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
public static ApplicationHandler getApplicationHandler() {
return applicationHandler;
}
public static InputHandler getInputHandler() {
return inputHandler;
}
}

View File

@ -16,11 +16,14 @@
package org.xbmc.kore.tests.ui;
import android.content.Context;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import org.junit.Ignore;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.ui.BaseMediaActivity;
@ -37,6 +40,16 @@ import static org.xbmc.kore.testhelpers.EspressoTestUtils.rotateDevice;
@Ignore
abstract public class BaseMediaActivityTests<T extends BaseMediaActivity> extends AbstractTestClass<T> {
@Override
protected void setSharedPreferences(Context context) {
}
@Override
protected void configureHostInfo(HostInfo hostInfo) {
}
/**
* Test if the initial state shows the hamburger icon
*/

View File

@ -16,6 +16,7 @@
package org.xbmc.kore.tests.ui.movies;
import android.content.Context;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
@ -25,6 +26,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.tests.ui.AbstractTestClass;
import org.xbmc.kore.ui.sections.video.MoviesActivity;
@ -46,6 +48,16 @@ public class RestoreSearchQueryListFragmentTest extends AbstractTestClass<Movies
return mActivityRule;
}
@Override
protected void setSharedPreferences(Context context) {
}
@Override
protected void configureHostInfo(HostInfo hostInfo) {
}
/**
* Simple test that checks if search query results in expected item(s)
*/

View File

@ -17,6 +17,7 @@
package org.xbmc.kore.tests.ui.music;
import android.app.Activity;
import android.content.Context;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
@ -26,6 +27,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.testhelpers.LoaderIdlingResource;
import org.xbmc.kore.tests.ui.AbstractTestClass;
@ -56,6 +58,16 @@ public class RestoreSearchQueryViewPagerTest extends AbstractTestClass<MusicActi
return mActivityRule;
}
@Override
protected void setSharedPreferences(Context context) {
}
@Override
protected void configureHostInfo(HostInfo hostInfo) {
}
/**
* Simple test that checks if search query results in expected item(s)
*

View File

@ -17,6 +17,7 @@
package org.xbmc.kore.tests.ui.music;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.SystemClock;
import android.support.test.rule.ActivityTestRule;
@ -31,6 +32,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.testhelpers.action.ViewActions;
import org.xbmc.kore.tests.ui.AbstractTestClass;
@ -68,6 +70,16 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
return musicActivityActivityTestRule;
}
@Override
protected void setSharedPreferences(Context context) {
}
@Override
protected void configureHostInfo(HostInfo hostInfo) {
}
@Override
public void setUp() throws Throwable {
super.setUp();

View File

@ -0,0 +1,168 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.tests.ui.remote.controlpad.eventserver;
import android.content.Context;
import android.support.test.rule.ActivityTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.eventclient.ButtonCodes;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.method.Input;
import org.xbmc.kore.testhelpers.TestUtils;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.tests.ui.AbstractTestClass;
import org.xbmc.kore.testutils.eventserver.EventPacket;
import org.xbmc.kore.testutils.eventserver.EventPacketBUTTON;
import org.xbmc.kore.testutils.eventserver.MockEventServer;
import org.xbmc.kore.ui.sections.remote.RemoteActivity;
import java.io.IOException;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.longClick;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static junit.framework.Assert.assertTrue;
public class ButtonTests extends AbstractTestClass<RemoteActivity> {
private static MockEventServer mockEventServer;
@Rule
public ActivityTestRule<RemoteActivity> remoteActivityActivityTestRule =
new ActivityTestRule<>(RemoteActivity.class);
@Override
protected ActivityTestRule<RemoteActivity> getActivityTestRule() {
return remoteActivityActivityTestRule;
}
@Override
protected void setSharedPreferences(Context context) {
Utils.setUseEventServerPreference(context, true);
}
@Override
protected void configureHostInfo(HostInfo hostInfo) {
hostInfo.setKodiVersionMajor(17);
}
@BeforeClass
public static void setupEventServer() throws Throwable {
mockEventServer = new MockEventServer();
mockEventServer.setListenPort(HostInfo.DEFAULT_EVENT_SERVER_PORT);
mockEventServer.start();
}
@After
public void resetState() {
mockEventServer.reset();
}
@AfterClass
public static void cleanup() throws IOException {
mockEventServer.shutdown();
}
@Test
public void leftControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.left)).perform(click());
testRemoteButton(ButtonCodes.REMOTE_LEFT);
}
@Test
public void rightControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.right)).perform(click());
testRemoteButton(ButtonCodes.REMOTE_RIGHT);
}
@Test
public void upControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.up)).perform(click());
testRemoteButton(ButtonCodes.REMOTE_UP);
}
@Test
public void downControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.down)).perform(click());
testRemoteButton(ButtonCodes.REMOTE_DOWN);
}
@Test
public void selectPadButtonTest() throws InterruptedException {
onView(withId(R.id.select)).perform(click());
testRemoteButton(ButtonCodes.REMOTE_SELECT);
}
//The following tests do not use the event server. They're included here
//to make sure they still work when the event server is enabled.
@Test
public void contextControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.context)).perform(click());
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.CONTEXTMENU);
}
@Test
public void infoControlPadButtonTest() throws InterruptedException {
HostManager.getInstance(getActivity()).getHostInfo().setKodiVersionMajor(17);
onView(withId(R.id.info)).perform(click());
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.INFO);
}
@Test
public void infoControlPadButtonLongClickTest() throws InterruptedException {
onView(withId(R.id.info)).perform(longClick());
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.PLAYERPROCESSINFO);
}
@Test
public void osdControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.osd)).perform(click());
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.OSD);
}
@Test
public void backControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.back)).perform(click());
TestUtils.testHTTPEvent(Input.Back.METHOD_NAME, null);
}
private void testRemoteButton(String buttonName) {
EventPacket packet = mockEventServer.getEventPacket();
assertTrue(packet != null);
assertTrue(packet.getPacketType() == EventPacket.PT_BUTTON);
assertTrue(((EventPacketBUTTON) packet).getButtonName().contentEquals(buttonName));
assertTrue(((EventPacketBUTTON) packet).getMapName().contentEquals(ButtonCodes.MAP_REMOTE));
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.tests.ui.remote.controlpad.eventserver;
import android.content.Context;
import android.support.test.rule.ActivityTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.method.Input;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.tests.ui.AbstractTestClass;
import org.xbmc.kore.testutils.eventserver.MockEventServer;
import org.xbmc.kore.ui.sections.remote.RemoteActivity;
import java.io.IOException;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.longClick;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static junit.framework.Assert.assertTrue;
public class KodiPreV17Tests extends AbstractTestClass<RemoteActivity> {
private static MockEventServer mockEventServer;
@Rule
public ActivityTestRule<RemoteActivity> remoteActivityActivityTestRule =
new ActivityTestRule<>(RemoteActivity.class);
@Override
protected ActivityTestRule<RemoteActivity> getActivityTestRule() {
return remoteActivityActivityTestRule;
}
@Override
protected void setSharedPreferences(Context context) {
Utils.setUseEventServerPreference(context, true);
}
@Override
protected void configureHostInfo(HostInfo hostInfo) {
hostInfo.setKodiVersionMajor(16);
}
@BeforeClass
public static void setupEventServer() throws Throwable {
mockEventServer = new MockEventServer();
mockEventServer.setListenPort(HostInfo.DEFAULT_EVENT_SERVER_PORT);
mockEventServer.start();
}
@After
public void resetState() {
mockEventServer.reset();
}
@AfterClass
public static void cleanup() throws IOException {
mockEventServer.shutdown();
}
@Test
public void infoControlPadButtonLongClickTest() throws InterruptedException {
HostManager.getInstance(getActivity()).getHostInfo().setKodiVersionMajor(16);
onView(withId(R.id.info)).perform(longClick());
String actionReceived = getInputHandler().getAction();
assertTrue(actionReceived != null);
assertTrue(actionReceived.contentEquals(Input.ExecuteAction.CODECINFO));
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.tests.ui.remote.controlpad.http;
import android.content.Context;
import android.support.test.rule.ActivityTestRule;
import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.method.Input;
import org.xbmc.kore.testhelpers.TestUtils;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.tests.ui.AbstractTestClass;
import org.xbmc.kore.ui.sections.remote.RemoteActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.longClick;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
public class ButtonTests extends AbstractTestClass<RemoteActivity> {
@Rule
public ActivityTestRule<RemoteActivity> remoteActivityActivityTestRule =
new ActivityTestRule<>(RemoteActivity.class);
@Override
protected ActivityTestRule<RemoteActivity> getActivityTestRule() {
return remoteActivityActivityTestRule;
}
@Override
protected void setSharedPreferences(Context context) {
Utils.setUseEventServerPreference(context, false);
}
@Override
protected void configureHostInfo(HostInfo hostInfo) {
hostInfo.setKodiVersionMajor(17);
}
@Test
public void leftControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.left)).perform(click());
TestUtils.testHTTPEvent(Input.Left.METHOD_NAME, null);
}
@Test
public void rightControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.right)).perform(click());
TestUtils.testHTTPEvent(Input.Right.METHOD_NAME, null);
}
@Test
public void upControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.up)).perform(click());
TestUtils.testHTTPEvent(Input.Up.METHOD_NAME, null);
}
@Test
public void downControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.down)).perform(click());
TestUtils.testHTTPEvent(Input.Down.METHOD_NAME, null);
}
@Test
public void selectPadButtonTest() throws InterruptedException {
onView(withId(R.id.select)).perform(click());
TestUtils.testHTTPEvent(Input.Select.METHOD_NAME, null);
}
@Test
public void contextControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.context)).perform(click());
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.CONTEXTMENU);
}
@Test
public void infoControlPadButtonTest() throws InterruptedException {
HostManager.getInstance(getActivity()).getHostInfo().setKodiVersionMajor(17);
onView(withId(R.id.info)).perform(click());
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.INFO);
}
@Test
public void infoControlPadButtonLongClickTest() throws InterruptedException {
onView(withId(R.id.info)).perform(longClick());
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.PLAYERPROCESSINFO);
}
@Test
public void osdControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.osd)).perform(click());
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.OSD);
}
@Test
public void backControlPadButtonTest() throws InterruptedException {
onView(withId(R.id.back)).perform(click());
TestUtils.testHTTPEvent(Input.Back.METHOD_NAME, null);
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.tests.ui.remote.controlpad.http;
import android.content.Context;
import android.support.test.rule.ActivityTestRule;
import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.jsonrpc.method.Input;
import org.xbmc.kore.testhelpers.TestUtils;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.tests.ui.AbstractTestClass;
import org.xbmc.kore.ui.sections.remote.RemoteActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.longClick;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
public class KodiPreV17Tests extends AbstractTestClass<RemoteActivity> {
@Rule
public ActivityTestRule<RemoteActivity> remoteActivityActivityTestRule =
new ActivityTestRule<>(RemoteActivity.class);
@Override
protected ActivityTestRule<RemoteActivity> getActivityTestRule() {
return remoteActivityActivityTestRule;
}
@Override
protected void setSharedPreferences(Context context) {
Utils.setUseEventServerPreference(context, false);
}
@Override
protected void configureHostInfo(HostInfo hostInfo) {
hostInfo.setKodiVersionMajor(16);
}
@Test
public void infoControlPadButtonLongClickTest() throws InterruptedException {
onView(withId(R.id.info)).perform(longClick());
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.CODECINFO);
}
}

View File

@ -66,14 +66,15 @@ public class Database {
public static HostInfo addHost(Context context) {
return addHost(context, "127.0.0.1", HostConnection.PROTOCOL_TCP,
HostInfo.DEFAULT_HTTP_PORT, HostInfo.DEFAULT_TCP_PORT);
HostInfo.DEFAULT_HTTP_PORT, HostInfo.DEFAULT_TCP_PORT, false);
}
public static HostInfo addHost(Context context, String hostname, int protocol, int httpPort, int tcpPort) {
public static HostInfo addHost(Context context, String hostname, int protocol, int httpPort,
int tcpPort, boolean useEventServer) {
return HostManager.getInstance(context).addHost("TestHost", hostname, protocol, httpPort,
tcpPort, null, null, "52:54:00:12:35:02", 9,
false, HostInfo.DEFAULT_EVENT_SERVER_PORT,
useEventServer, HostInfo.DEFAULT_EVENT_SERVER_PORT,
HostInfo.DEFAULT_KODI_VERSION_MAJOR,
HostInfo.DEFAULT_KODI_VERSION_MINOR,
HostInfo.DEFAULT_KODI_VERSION_REVISION,

View File

@ -0,0 +1,128 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.testutils.eventserver;
import org.xbmc.kore.eventclient.Packet;
import org.xbmc.kore.utils.LogUtils;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* Class that implements a single event packet.
* <pre>
* -----------------------------
* | -H1 Signature ("XBMC") | - 4 x CHAR 4B
* | -H2 Version (eg. 2.0) | - 2 x UNSIGNED CHAR 2B
* | -H3 PacketType | - 1 x UNSIGNED SHORT 2B
* | -H4 Sequence number | - 1 x UNSIGNED LONG 4B
* | -H5 No. of packets in msg | - 1 x UNSIGNED LONG 4B
* | -H6 Payloadsize of packet | - 1 x UNSIGNED SHORT 2B
* | -H7 Client's unique token | - 1 x UNSIGNED LONG 4B
* | -H8 Reserved | - 10 x UNSIGNED CHAR 10B
* |---------------------------|
* | -P1 payload | -
* -----------------------------
* </pre>
*/
abstract public class EventPacket {
private static final String TAG = LogUtils.makeLogTag(EventPacket.class);
//Package types
public final static byte PT_BUTTON = 0x03;
private String signature;
private String version;
private int packetType;
private long sequenceNumber;
private long numberOfPackets;
private int payloadSize;
private long token;
private byte[] payload;
private EventPacket() {}
EventPacket(byte[] packet) {
signature = new String(new byte[] {packet[0], packet[1], packet[2], packet[3]});
version = ((int) packet[4]) + "." + ((int) packet[5]);
packetType = ByteBuffer.wrap(packet, 6, 2).getChar();
sequenceNumber = ByteBuffer.wrap(packet, 8, 4).getInt();
numberOfPackets = ByteBuffer.wrap(packet, 12, 4).getInt();
payloadSize = ByteBuffer.wrap(packet, 16, 2).getChar();
token = ByteBuffer.wrap(packet, 18, 4).getInt();
//Reserved 22 - 32
payload = new byte[payloadSize];
ByteBuffer.wrap(packet, 32, payloadSize).get(payload);
}
@Override
public String toString() {
return signature + ":" +
version + ":" +
packetType + ":" +
sequenceNumber + ":" +
numberOfPackets + ":" +
payloadSize + ":" +
token+ ":" +
payload;
}
public int getPacketType() {
return packetType;
}
public byte[] getPayload() {
return payload;
}
/**
* Returns the packet type from a {@link Packet} as a single byte.
* <br/>
* Note that, although the specification specifies two bytes,
* we only use a single byte in {@link Packet} for the packet types.
* @param packet
* @return second byte of packet type
*/
static public byte getPacketType(byte[] packet) {
return packet[7];
}
/**
* Gets the string from payload terminated by 0x00.
* @param payload byte array holding the characters
* @param offset starting offset of string
* @return string from payload or null if not found
*/
String getStringFromPayload(byte[] payload, int offset) {
int strTerminatorIndex = offset;
for (; strTerminatorIndex < payload.length; strTerminatorIndex++) {
if (payload[strTerminatorIndex] == 0x00)
break;
}
if (strTerminatorIndex == payload.length)
return null;
int stringLength = strTerminatorIndex - offset;
byte[] bytes = new byte[stringLength];
System.arraycopy(payload, offset, bytes, 0, stringLength);
return new String(bytes);
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.testutils.eventserver;
import java.nio.ByteBuffer;
public class EventPacketBUTTON extends EventPacket {
private short code;
private String mapName;
private String buttonName;
private boolean repeat;
private boolean down;
private boolean queue;
private short amount;
private byte axis;
private short flags;
public EventPacketBUTTON(byte[] packet) {
super(packet);
byte[] payload = getPayload();
code = ByteBuffer.wrap(payload, 0, 2).getShort();
flags = ByteBuffer.wrap(payload, 2, 2).getShort();
amount = ByteBuffer.wrap(payload, 4, 2).getShort();
mapName = getStringFromPayload(payload, 6);
int nextStringPosition = 6 + mapName.getBytes().length + 1;
buttonName = getStringFromPayload(payload, nextStringPosition);
}
public String getButtonName() {
return buttonName;
}
public String getMapName() {
return mapName;
}
@Override
public String toString() {
return super.toString() +
", code: " + code +
", flags: " + flags +
", amount: " + amount +
", mapName: " + mapName +
", buttonName: " + buttonName;
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.testutils.eventserver;
import org.xbmc.kore.utils.LogUtils;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class MockEventServer extends Thread {
private static final String TAG = LogUtils.makeLogTag(MockEventServer.class);
private int listenPort = 9997;
private boolean keepRunning;
private EventPacket packet;
private DatagramSocket datagramSocket;
public MockEventServer() {
}
public void setListenPort(int portNumber) {
this.listenPort = portNumber;
}
public void run() {
try {
datagramSocket = new DatagramSocket(this.listenPort);
} catch (SocketException e) {
System.out.println("MockEventServer: Failed to open socket: " + e.getMessage());
return;
}
keepRunning = true;
while(keepRunning) {
byte[] buf = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
try {
datagramSocket.receive(datagramPacket);
packet = new EventPacketBUTTON(datagramPacket.getData());
} catch (IOException e) {
System.out.println("MockEventServer: error receiving packet: " + e.getMessage());
}
}
}
/**
* Returns the last received packet
* @return
*/
public EventPacket getEventPacket() {
return packet;
}
/**
* Stops the server from listening for new packets
*/
public void shutdown() {
keepRunning = false;
datagramSocket.close();
}
/**
* Resets the state of the event server
*/
public void reset() {
packet = null;
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.testutils.tcpserver.handlers;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.jsonrpc.method.Input;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
import org.xbmc.kore.utils.LogUtils;
import java.util.ArrayList;
/**
* Simulates Input JSON-RPC API
*/
public class InputHandler implements JSONConnectionHandlerManager.ConnectionHandler {
private static final String TAG = LogUtils.makeLogTag(InputHandler.class);
private static final String ACTION = "action";
private static final String PARAMS_NODE = "params";
private String action;
private String methodName;
@Override
public ArrayList<JsonResponse> getNotifications() {
return null;
}
@Override
public void reset() {
}
@Override
public String[] getType() {
return new String[]{Input.ExecuteAction.METHOD_NAME,
Input.Back.METHOD_NAME,
Input.Up.METHOD_NAME,
Input.Down.METHOD_NAME,
Input.Left.METHOD_NAME,
Input.Right.METHOD_NAME,
Input.Select.METHOD_NAME,
};
}
@Override
public ArrayList<JsonResponse> getResponse(String method, ObjectNode jsonRequest) {
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
methodName = method;
switch (method) {
case Input.ExecuteAction.METHOD_NAME:
action = jsonRequest.get(PARAMS_NODE).get(ACTION).asText();
break;
case Input.Left.METHOD_NAME:
case Input.Right.METHOD_NAME:
case Input.Up.METHOD_NAME:
case Input.Down.METHOD_NAME:
case Input.Select.METHOD_NAME:
// These inputs do not have an action
break;
default:
LogUtils.LOGD(TAG, "method: " + method + ", not implemented");
}
return jsonResponses;
}
/**
* Returns the last received action
* @return
*/
public String getAction() {
return action;
}
/**
* Returns the last received method name
* @return
*/
public String getMethodName() {
return methodName;
}
}

View File

@ -65,7 +65,7 @@ public class JSONConnectionHandlerManager implements MockTcpServer.TcpServerConn
/**
* Used to get any notifications from the handler.
* @return {@link JsonResponse} that should be sent to the client
* @return {@link JsonResponse} that should be sent to the client or null if there are no notifications
*/
ArrayList<JsonResponse> getNotifications();