Rename "Canvas" to "Screen" + clean up integration tests.

This commit is contained in:
alexiscatnip
2022-01-24 16:21:22 +08:00
parent be0ec385dd
commit cda1114254
21 changed files with 9548 additions and 1342 deletions
+3 -3
View File
@@ -37,16 +37,16 @@ public class ButtonTriggerViewTransition : MonoBehaviour
{
if (canvasTypeToPush == CanvasType.NONE)
{
ServiceLocator.Instance.Get<UIService>().canvasManager.PopCanvas();
ServiceLocator.Instance.Get<UIService>().ScreensManager.PopCanvas();
return;
}
if (popCurrent)
{
ServiceLocator.Instance.Get<UIService>().canvasManager.PopAndPush(canvasTypeToPush);
ServiceLocator.Instance.Get<UIService>().ScreensManager.PopAndPush(canvasTypeToPush);
} else
{
ServiceLocator.Instance.Get<UIService>().canvasManager.Push(canvasTypeToPush);
ServiceLocator.Instance.Get<UIService>().ScreensManager.Push(canvasTypeToPush);
}
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ namespace Raindrop.Disk
// returns SD card if the bool is true OR the sd card is not available.
// otherwise i will return internal-but-shared storage
private static string GetAndroidExternalFilesDir(bool preferSDcard)
public static string GetAndroidExternalFilesDir(bool preferSDcard)
{
using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
@@ -38,7 +38,7 @@ namespace Raindrop.Services.Bootstrap
}
//2. ui services
var cm = GetComponentInChildren<CanvasManager>();
var cm = GetComponentInChildren<ScreensManager>();
if (cm == null)
Debug.LogError("canvasmanager not present");
var mm = GetComponentInChildren<ModalManager>();
+9 -10
View File
@@ -21,16 +21,15 @@ namespace Raindrop.Services
private GridClient client { get { return instance.Client; } }
// canvases are stack-based. only 1 is top-most and active at any time.
private CanvasManager _canvasManager;
public CanvasManager canvasManager { set { _canvasManager = value; } get { return _canvasManager; } }
public ScreensManager ScreensManager { set; get; }
// modals are single-display. however, there is a modal queue, such that when the current modal is dismissed, the next-in-queue will appear.
//care has to be taken not to spam the user with modals.
private ModalManager _modalManager;
public ModalManager modalManager { set { _modalManager = value; } get { return _modalManager; } }
public ModalManager modalManager { set; get; }
public UIService(CanvasManager cm, ModalManager mm)
public UIService(ScreensManager cm, ModalManager mm)
{
canvasManager = cm;
ScreensManager = cm;
modalManager = mm;
// UI depends on raindrop business layer.
@@ -77,13 +76,13 @@ namespace Raindrop.Services
protected void startUIInitialView()
{
canvasManager.resetToInitialScreen();
ScreensManager.resetToInitialScreen();
modalManager.showModalNotification("Disclaimer", "This software is a work in progress. There is no guarantee about its stability. ");
}
public GameObject getCurrentForegroundPresenter()
{
return canvasManager.getForegroundCanvas();
return ScreensManager.getForegroundCanvas();
}
private void RegisterClientEvents(GridClient client)
@@ -140,7 +139,7 @@ namespace Raindrop.Services
{
modalManager.showModalNotification("Logged out", "you have/were logged out");
canvasManager.resetToInitialScreen();
ScreensManager.resetToInitialScreen();
//tsb3D.Enabled = tbtnVoice.Enabled = disconnectToolStripMenuItem.Enabled =
//tbtnGroups.Enabled = tbnObjects.Enabled = tbtnWorld.Enabled = tbnTools.Enabled = tmnuImport.Enabled =
@@ -166,7 +165,7 @@ namespace Raindrop.Services
if (e.Reason == NetworkManager.DisconnectType.ClientInitiated) return;
netcom_ClientLoggedOut(sender, EventArgs.Empty);
canvasManager.resetToInitialScreen();
ScreensManager.resetToInitialScreen();
//if (instance.GlobalSettings["auto_reconnect"].AsBoolean())
//{
@@ -9,7 +9,7 @@ using UnityEngine.Serialization;
//helper class that helps to pop, push, stack canvases.
//singleton.
//on awake, it searches children for all canvases.
public class CanvasManager : MonoBehaviour
public class ScreensManager : MonoBehaviour
{
[Tooltip("The canvases that are available to use.")]
public List<CanvasIdentifier> canvasControllerList = new List<CanvasIdentifier>();
@@ -127,7 +127,7 @@ public class CanvasManager : MonoBehaviour
// pop the current login screen, and push the game view. This is for viewing chats offline.
// planned for debug only.
public void loginWithoutNetworking()
public void LoginWithoutNetworking()
{
while ((activeCanvasStack.Count() != 0))
{
+1 -1
View File
@@ -52,7 +52,7 @@ public class EulaView : MonoBehaviour
if (instance.GlobalSettings["EulaAccepted"] == false)
return;
ServiceLocator.Instance.Get<UIService>().canvasManager.resetToInitialScreen();
ServiceLocator.Instance.Get<UIService>().ScreensManager.resetToInitialScreen();
}
private void onToggleChanged(bool isEulaAccepted)
@@ -50,10 +50,6 @@ public class ModalPresenter : MonoBehaviour
{
BackgroundButton.onClick.AsObservable().Subscribe(_ => closeModal()); //when clicked, runs this method.
}
else
{
Debug.LogError("BackgroundButton failed");
}
if (titletext.GetComponent<TMP_Text>() == null)
{
@@ -99,9 +95,4 @@ public class ModalPresenter : MonoBehaviour
// StartCoroutine(SlowFade_Coroutine(1500));
}
private CanvasRenderer getRenderer()
{
return renderer;
}
}
+3 -3
View File
@@ -62,7 +62,7 @@ namespace Raindrop.Presenters
configTimer = new System.Threading.Timer(SaveConfig, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
ChatButton.onClick.AsObservable().Subscribe(_ => OnChatBtnClick()); //when clicked, runs this method.
MapButton.onClick.AsObservable().Subscribe(_ => OnMapBtnClick()); //when clicked, runs this method.
MapButton?.onClick.AsObservable().Subscribe(_ => OnMapBtnClick()); //when clicked, runs this method.
SoundToggle.OnValueChangedAsObservable().Subscribe(_ => OnToggleSounds(_));
//simName.AsObservable().Subscribe(_ => UpdateSimLocDisplay(_));
@@ -163,11 +163,11 @@ namespace Raindrop.Presenters
public void OnChatBtnClick()
{
uimanager.canvasManager.Push(CanvasType.Chat);
uimanager.ScreensManager.Push(CanvasType.Chat);
}
public void OnMapBtnClick()
{
uimanager.canvasManager.Push(CanvasType.Map);
uimanager.ScreensManager.Push(CanvasType.Map);
}
+1 -1
View File
@@ -213,7 +213,7 @@ namespace Raindrop.Presenters
Close_LoginModal_Slow();
uimanager.canvasManager.PopAndPush(CanvasType.Game);
uimanager.ScreensManager.PopAndPush(CanvasType.Game);
//instance.UI.canvasManager.popCanvas();
LoginButton.interactable = true;
break;
+9341 -1138
View File
File diff suppressed because it is too large Load Diff
+6 -7
View File
@@ -21,7 +21,6 @@ namespace Raindrop.Tests.ImagingTests
{
private static RaindropInstance instance = new RaindropInstance(new GridClient());
public static string clientDir = instance.ClientDir;
public string appDataDir = clientDir + "/ImagingTests/menhara_out_persistentDataPath.jpg";
public static string _inputImageSubPath = "test/menhara.jp2";
[OneTimeSetUp]
@@ -61,18 +60,18 @@ namespace Raindrop.Tests.ImagingTests
var outbytes = texture.EncodeToJPG(100);
string appDataDir = clientDir + "/ImagingTests/menhara_out_clientDir.jpg";
Helper.WriteToFile(outbytes, appDataDir);
#if UNITY_EDITOR
#elif UNITY_ANDROID //warn, this is true if we are in editor...
string outPath_GetAndroidExternalFilesDir_internal =
GetAndroidExternalFilesDir(false) + "/Pictures/menhara_out_GetAndroidExternalFilesDir_internal.jpg";
Disk.DirectoryHelpers.GetAndroidExternalFilesDir(false) + "/ImagingTests/menhara_out_GetAndroidExternalFilesDir_internal.jpg";
string outPath_GetAndroidExternalFilesDir_external =
GetAndroidExternalFilesDir(true) + "/Pictures/menhara_out_GetAndroidExternalFilesDir_external.jpg";
Disk.DirectoryHelpers.GetAndroidExternalFilesDir(true) + "/ImagingTests/menhara_out_GetAndroidExternalFilesDir_external.jpg";
Helper.WriteToFile(outbytes, outPath_GetAndroidExternalFilesDir_internal);
Helper.WriteToFile(outbytes, outPath_GetAndroidExternalFilesDir_external);
WriteToFile(outbytes, outPath_GetAndroidExternalFilesDir_internal);
WriteToFile(outbytes, outPath_GetAndroidExternalFilesDir_external);
WriteToFile(outbytes, outPath_persistentDataPath);
#endif
float timeEnd = Time.realtimeSinceStartup;
@@ -84,7 +84,6 @@ namespace Raindrop.Tests
}
));
Assert.Pass();
yield break;
}
@@ -109,8 +108,6 @@ namespace Raindrop.Tests
void FinishWebRequest(IAsyncResult ar)
{
Debug.Log("net done.");
Assert.Pass();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c744dde4ee1b435fad53a48433ba0c38
timeCreated: 1643006989
@@ -0,0 +1,47 @@
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.UI;
namespace Tests.RaindropIntegrationTests.InputSubroutines
{
// Subroutines to perform login.
public class Login
{
// type user creds and click the login button.
public static IEnumerator StartLogin(string username, string password)
{
TypeUserAndPassIntoLoginPanel(username, password);
yield return new WaitForSeconds(2);
UIHelpers.Click_ButtonByUnityName("LoginBtn");
yield return new WaitForSeconds(20);
}
public static void TypeUserAndPassIntoLoginPanel(string _username, string _password)
{
UIHelpers.Keyboard_TMPInputField_ByUnityName("UserTextField", _username);
UIHelpers.Keyboard_TMPInputField_ByUnityName("PassTextField", _password);
}
//accept the eula:
/* 1. toggle true
* 2. click "next" btn
*/
// on the welcome screen, click the go button.
public static void accepttheeula()
{
string eulaCheckbox = "AgreeToggle";
var checkboxEULA = GameObject.Find(eulaCheckbox);
Assert.IsNotNull(checkboxEULA, "Missing checkbox " + eulaCheckbox);
checkboxEULA.GetComponent<Toggle>().onValueChanged.Invoke(true);
string eulaCloseBtn = "NextButton";
Assert.IsTrue(UIHelpers.Click_ButtonByUnityName(eulaCloseBtn));
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 090e146372324654b989d47f541707e3
timeCreated: 1642991684
@@ -0,0 +1,43 @@
using Lean.Gui;
using NUnit.Framework;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Tests.RaindropIntegrationTests.InputSubroutines
{
static internal class UIHelpers
{
public static bool Click_ButtonByUnityName(string gameObjectName)
{
var btn = GameObject.Find(gameObjectName);
Assert.IsNotNull(btn, "Missing button supposed to have name: " + gameObjectName);
if (btn.GetComponent<Button>())
{
btn.GetComponent<Button>().onClick.Invoke();
return true;
}
if (btn.GetComponent<LeanButton>())
{
btn.GetComponent<LeanButton>().OnClick.Invoke();
return true;
}
return false;
}
public static void Keyboard_TMPInputField_ByUnityName(string go_name, string input)
{
var go = GameObject.Find(go_name);
Assert.True(go != null, "unable to find gameobject of name " + go_name);
var tmp = go.GetComponent<TMP_InputField>();
Assert.True(tmp != null, "Gameobject + " + go_name + " does not have TMP inputfield component ");
tmp.ActivateInputField();
tmp.text = input;
tmp.onValueChanged.Invoke(input);
tmp.onSubmit.Invoke(input);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 90529e2efab64e25a18162bcd430d18a
timeCreated: 1643010815
@@ -1,19 +1,9 @@
using NUnit.Framework;
using OpenMetaverse;
using System.Collections;
using Lean.Gui;
using NUnit.Framework;
using Raindrop;
using Raindrop.Netcom;
using Raindrop.Presenters;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Lean.Gui;
using NUnit.Framework.Internal;
using OpenMetaverse.Assets;
using OpenMetaverse.Imaging;
using OpenMetaverse.ImportExport.Collada14;
using Raindrop.Services;
using TMPro;
using UnityEngine;
@@ -21,7 +11,7 @@ using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using UnityEngine.UI;
namespace Raindrop.Tests
namespace Tests.RaindropIntegrationTests
{
/*
* UI-intensive tests for the login functionality. the main scene will be loaded.
@@ -30,10 +20,10 @@ namespace Raindrop.Tests
[TestFixture()]
public class LoginTests
{
private static string _username;
private static string _password;
private static string _username = "***REMOVED*** Resident"; // fixme: move this to some xml
private static string _password = "***REMOVED***";
private RaindropNetcom netcom { get { return instance.Netcom; } }
private RaindropInstance instance { get { return ServiceLocator.ServiceLocator.Instance.Get<RaindropInstance>(); } }
private RaindropInstance instance { get { return Raindrop.ServiceLocator.ServiceLocator.Instance.Get<RaindropInstance>(); } }
[OneTimeSetUp]
public void OneTimeSetUp()
@@ -43,7 +33,7 @@ namespace Raindrop.Tests
}
[UnityTest]
//assert pass is working (teardown is ok)
//assert that the loading main scene have no huge errors.
public IEnumerator AbleToAssertPass()
{
Assert.Pass();
@@ -56,7 +46,9 @@ namespace Raindrop.Tests
* 2. check backend API, login is true
* 3. [UI] press logout button.
* 4. check is logged out
* 5. passed.
*
* 5. then do the above again.
* 6. passed
*/
public IEnumerator LoginLogoutTest()
{
@@ -64,86 +56,61 @@ namespace Raindrop.Tests
var vm = Get_ViewsManager();
//1. get the refence to the UI service.
var srvLocator = ServiceLocator.ServiceLocator.Instance;
var UISrv = srvLocator.Get<UIService>();
// srvLocator.Get<UIService>().resetUI();
var srvLocator = Raindrop.ServiceLocator.ServiceLocator.Instance;
UIService uiSrv = null;
try
{
uiSrv = srvLocator.Get<UIService>();
}
catch
{
Assert.Fail("UIService unavailable.");
}
// uiSrv.resetUI();
//1a. accept the eula if needed.
if (UISrv.canvasManager.topCanvas.canvasType == CanvasType.Eula)
if (uiSrv.ScreensManager.topCanvas.canvasType == CanvasType.Eula)
{
// well, we need to agree to eula first.
Utils.UIHelpers.accepttheeula();
}
//1b. we are on the welcome screen. now navigate to the login screen.
if (UISrv.canvasManager.topCanvas.canvasType == CanvasType.Welcome)
{
InputSubroutines.Login.accepttheeula();
yield return new WaitForSeconds(2);
Utils.UIHelpers.Click_Button_Welcome2LoginScreen();
}
else
{
Assert.Fail("expected to be on the welcome screen!");
}
SetClientSettings();
//1b. we are on the login screen. do login. assert logged in.
Assert.IsTrue(UISrv.canvasManager.topCanvas.canvasType == CanvasType.Login);
yield return new WaitForSeconds(2); // if you remvoe this one you will fail the test.
LoginPresenterIsAvailable(vm);
TypeUserAndPassIntoLoginPanel();
yield return new WaitForSeconds(2);
Utils.UIHelpers.ClickButtonByUnityName("LoginBtn");
//assert the backend API; that we are logged in.
yield return new WaitForSeconds(20);
Assert.True(instance.Client.Network.Connected == true, "check API that we are logged in");
//finally, disconnect. assert disconnected.
Utils.UIHelpers.Click_DisconnectButton();
yield return new WaitForSeconds(5);
Assert.True(instance.Client.Network.Connected == false, "check API that we are logged out");
yield return new WaitForSeconds(30);
//try to login again!
//1b. we are on the welcome screen. now navigate to the login screen.
if (UISrv.canvasManager.topCanvas.canvasType == CanvasType.Welcome)
int times = 2;
for (int i = 0; i < times; i++)
{
yield return new WaitForSeconds(2);
Utils.UIHelpers.Click_Button_Welcome2LoginScreen();
}
else
{
Assert.Fail("expected to be on the welcome screen!");
}
Assert.IsTrue(UISrv.canvasManager.topCanvas.canvasType == CanvasType.Login);
Assert.True(instance.Client.Network.Connected == false, "check API that we are logged out");
LoginPresenterIsAvailable(vm);
TypeUserAndPassIntoLoginPanel();
yield return new WaitForSeconds(5);
Utils.UIHelpers.ClickButtonByUnityName("LoginBtn");
//assert the backend API; that we are logged in.
yield return new WaitForSeconds(20);
Assert.True(instance.Client.Network.Connected == true, "check API that we are logged in");
//1b. we are on the welcome screen. now navigate to the login screen.
if (uiSrv.ScreensManager.topCanvas.canvasType == CanvasType.Welcome)
{
yield return new WaitForSeconds(2);
yield return Utils.UIHelpers.Click_Button_Welcome2LoginScreen();
}
else
{
Assert.Fail("expected to be on the welcome screen!");
}
//1b. we are on the login screen. do login. assert logged in.
Assert.IsTrue(uiSrv.ScreensManager.topCanvas.canvasType == CanvasType.Login);
LoginPresenterIsAvailable(vm);
//finally, disconnect. assert disconnected.
Utils.UIHelpers.Click_DisconnectButton();
yield return new WaitForSeconds(5);
Assert.True(instance.Client.Network.Connected == false, "check API that we are logged out");
yield return InputSubroutines.Login.StartLogin(_username, _password);
Assert.Pass();
Debug.Log("test is ok!");
yield return new WaitForSeconds(30);
//assert the backend API; that we are logged in.
Assert.True(instance.Client.Network.Connected == true, "check API that we are logged in");
//finally, disconnect. assert disconnected.
InputSubroutines.UIHelpers.Click_ButtonByUnityName("LogoutBtn");
yield return new WaitForSeconds(12);
Assert.True(instance.Client.Network.Connected == false, "check API that we are logged out");
yield return new WaitForSeconds(12);
}
yield break;
}
private static GameObject Get_ViewsManager()
@@ -160,18 +127,10 @@ namespace Raindrop.Tests
private static void LoginPresenterIsAvailable(GameObject vm)
{
var loginPresenter
= vm.GetComponent<CanvasManager>().getForegroundCanvas().GetComponent<LoginPresenter>();
= vm.GetComponent<ScreensManager>().getForegroundCanvas().GetComponent<LoginPresenter>();
Assert.True(loginPresenter != null);
}
private static void TypeUserAndPassIntoLoginPanel()
{
_username = "***REMOVED*** Resident";
Utils.UIHelpers.Set_TMPInputField_ofGameObjectName("UserTextField", _username);
_password = "***REMOVED***";
Utils.UIHelpers.Set_TMPInputField_ofGameObjectName("PassTextField", _password);
}
private void SetClientSettings()
{
//Extra: set reducedsettings so that we do not use too many dependencies
@@ -183,6 +142,7 @@ namespace Raindrop.Tests
instance.Client.Settings.STORE_LAND_PATCHES = false;
instance.Client.Settings.STORE_LAND_PATCHES = false;
}
public class Utils
{
/*
@@ -190,62 +150,13 @@ namespace Raindrop.Tests
*/
public class UIHelpers
{
public static bool ClickButtonByUnityName(string gameObjectName)
public static IEnumerator Click_Button_Welcome2LoginScreen()
{
var btn = GameObject.Find(gameObjectName);
Assert.IsNotNull(btn, "Missing button supposed to have name: " + gameObjectName);
if (btn.GetComponent<Button>())
{
btn.GetComponent<Button>().onClick.Invoke();
return true;
}
if (btn.GetComponent<LeanButton>())
{
btn.GetComponent<LeanButton>().OnClick.Invoke();
return true;
}
return false;
}
public static void Set_TMPInputField_ofGameObjectName(string go_name, string input)
{
var go = GameObject.Find(go_name);
Assert.True(go != null, "unable to find gameobject of name " + go_name);
var tmp = go.GetComponent<TMP_InputField>();
Assert.True(tmp != null, "Gameobject + " + go_name + " does not have TMP inputfield component ");
tmp.ActivateInputField();
tmp.text = input;
tmp.onValueChanged.Invoke(input);
tmp.onSubmit.Invoke(input);
}
public static void Click_DisconnectButton()
{
ClickButtonByUnityName("LogoutBtn");
}
//accept the eula:
/* 1. toggle true
* 2. click "next" btn
*/
// on the welcome screen, click the go button.
public static void accepttheeula()
{
string eulaCheckbox = "AgreeToggle";
var checkboxEULA = GameObject.Find(eulaCheckbox);
Assert.IsNotNull(checkboxEULA, "Missing checkbox " + eulaCheckbox);
checkboxEULA.GetComponent<Toggle>().onValueChanged.Invoke(true);
string eulaCloseBtn = "NextButton";
Assert.IsTrue(LoginTests.Utils.UIHelpers.ClickButtonByUnityName(eulaCloseBtn));
}
public static void Click_Button_Welcome2LoginScreen()
{
Assert.IsTrue(LoginTests.Utils.UIHelpers.ClickButtonByUnityName("LetsGo!"));
Assert.IsTrue(
InputSubroutines.UIHelpers.Click_ButtonByUnityName("LetsGo!")
);
yield return new WaitForSeconds(2); // if you remvoe this one you will fail the test.
}
}
}
@@ -2,10 +2,12 @@
using NUnit.Framework;
using OpenMetaverse;
using Raindrop.Map.Model;
using Raindrop.ServiceLocator;
using UnityEngine;
using UnityEngine.TestTools;
using Utils = OpenMetaverse.Utils;
namespace Raindrop.Tests.RaindropTests
namespace Tests.RaindropIntegrationTests
{
//test the map service.
public class MapServiceTests
@@ -17,11 +19,11 @@ namespace Raindrop.Tests.RaindropTests
{
Raindrop.Unity.SceneBootstrapperGenerator.Init();
yield return new WaitForSeconds(2);
Assert.True(ServiceLocator.ServiceLocator.Instance != null);
Assert.True(ServiceLocator.Instance != null);
Raindrop.Unity.SceneBootstrapperGenerator.AddMainThreadDispatcher();
BeginMapFetcher();
var fetcher = ServiceLocator.ServiceLocator.Instance.Get<MapService>();
var fetcher = ServiceLocator.Instance.Get<MapService>();
//initally, the maptile is just a empty pocket
bool isReady;
@@ -42,10 +44,10 @@ namespace Raindrop.Tests.RaindropTests
void BeginMapFetcher()
{
if (!ServiceLocator.ServiceLocator.Instance.IsRegistered<MapService>())
if (!ServiceLocator.Instance.IsRegistered<MapService>())
{
Debug.Log("UIBootstrapper creating and registering MapBackend.MapFetcher!");
ServiceLocator.ServiceLocator.Instance.Register<MapService>(new MapService());
ServiceLocator.Instance.Register<MapService>(new MapService());
//return;
}
}
+10 -5
View File
@@ -7,16 +7,21 @@ using UnityEngine.Events;
using UnityEngine.UI;
[RequireComponent(typeof(Button))]
public class logoutButton : MonoBehaviour
public class LogoutButton : MonoBehaviour
{
void Start()
//get own references.
void Awake()
{
Button btn = this.GetComponent<Button>();
btn.onClick.AddListener(logout);
btn.onClick.AddListener(Logout);
}
//get others' references, if required.
void Start()
{
}
private void logout()
private void Logout()
{
Debug.Log("logout requested by user UI");
ServiceLocator.Instance.Get<RaindropInstance>().Netcom.Logout();