/* Copyright (c) Citrix Systems, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Moq; using NUnit.Framework; using XenAdmin.Actions; using XenAdmin.Core; using XenAPI; namespace XenAdminTests.XenModelTests { /// /// This unit test relies on a list of call versions. For ease, they are included /// as a csv file /// /// To update the csv call list: /// 1. Build your own version on the api bindings. /// 2. Look in the generated code output directory (not the code that's been packaged) eg. build.hg/output/api-bindings /// 3. Copy out the "callVersions.csv" and replace the one in TestResources /// /// To Add New Actions: /// 1. Go down to the container of actions called "Actions" /// 2. Add enough unit test code to get your action to run - this is failrly minimal /// /// To Add a new version: /// 1. Add a const string containing the correct name as extracted into the csv file /// 2. Add this to the test ctor /// 3. Add a DatabaseInfo member /// 4. Check the unsupportedVersions are up to date /// [TestFixture] internal class TestAPICallVersions : UnitTester_TestFixture { public TestAPICallVersions() : base(rio, miami, symc, orlando, george, midnight, cowley, boston, sanibel, tampa, tallahassee, clearwater, sarasota, thefuture) { } private void SetupConnection(DatabaseInfo id) { Mock host = ObjectManager.NewXenObject(id.name); host.Setup(h => h.ProductVersion).Returns(id.version); ObjectManager.MockConnectionFor(id.name).Setup(c => c.Resolve(It.IsAny>())).Returns(host.Object); } [Test] public void CompareCalls() { ReadCallList(); foreach (DatabaseInfo info in versionData.Where( v=> !unsupportedVersions.Contains(v.name))) { SetupConnection(info); foreach (AsyncAction myAction in Actions(info.name)) { List calls = ExtractApiCallList(myAction); VerifyCalls(calls, info); } } } #region Data private const string tampa = "tampa"; private const string boston = "boston"; private const string rio = "rio"; private const string sanibel = "sanibel"; private const string cowley = "cowley"; private const string george = "george"; private const string miami = "miami"; private const string midnight = "midnight-ride"; private const string orlando = "orlando"; private const string symc = "symc"; private const string tallahassee = "tallahassee"; private const string clearwater = "clearwater"; private const string sarasota = "sarasota"; private const string thefuture = "Oct 21, 2015"; /// /// Complete lookup of version numbers /// private readonly List versionData = new List { new DatabaseInfo {name = rio, version = "4.0.0"}, new DatabaseInfo {name = miami, version = "4.1.0"}, new DatabaseInfo {name = symc, version = "4.1.0"}, new DatabaseInfo {name = orlando, version = "5.0.0"}, new DatabaseInfo {name = george, version = "5.5.0"}, new DatabaseInfo {name = midnight, version = "5.6.0"}, new DatabaseInfo {name = cowley, version = "5.6.100"}, new DatabaseInfo {name = boston, version = "6.0.0"}, new DatabaseInfo {name = sanibel, version = "6.0.2"}, new DatabaseInfo {name = tampa, version = "6.1.0"}, new DatabaseInfo {name = tallahassee, version = "6.1.1"}, new DatabaseInfo {name = clearwater, version = "6.1.2"}, new DatabaseInfo {name = sarasota, version = "6.5.0"}, new DatabaseInfo {name = thefuture, version = "9999.9999.9999"}, }; /// /// These versions are not checked for compatability /// private readonly List unsupportedVersions = new List { rio, miami, symc, orlando, george }; /// /// Additional calls that are added to the call list or overloads for the list /// private readonly Dictionary additionalCalls = new Dictionary { {"vif_destroy", "rio"} }; /// /// List of actions to test - you need to add one per test of interest /// TODO: Probably best to add this to a factory at some point /// TODO: Add more actions /// /// /// private IEnumerable Actions(string id) { yield return ActionFactory.MockActionFor(id, typeof (DeleteVIFAction)); yield return ActionFactory.MockActionFor(id, typeof(AddRemoveRolesAction)); } private Dictionary CallsInProductSince { get; set; } #endregion #region Helper Classes And Methods private class DatabaseInfo { public string name; public string version; } private void ReadCallList() { CallsInProductSince = additionalCalls; using (StreamReader sr = new StreamReader(TestResource("callVersions.csv"))) { while (!sr.EndOfStream) { string line = sr.ReadLine(); if (String.IsNullOrEmpty(line)) continue; string[] words = line.Split(','); if (!CallsInProductSince.ContainsKey(words[0])) CallsInProductSince.Add(words[0], words[1]); } } } private void VerifyCalls(List callsMade, DatabaseInfo dbInfo) { Assert.That(callsMade.Count, Is.GreaterThan(0), "The action made no api calls"); foreach (string call in callsMade) { if(call.Contains("_get_allowed_operations")) continue; if(!CallsInProductSince.ContainsKey(call)) { string msg = String.Format("Call missing '{0}'- suggest an update of the call list", call); Assert.Fail(msg); } string closureCall = call; string callVersionNumber = versionData.Find(d => d.name == CallsInProductSince[closureCall]).version; Assert.IsTrue(Helpers.productVersionCompare(callVersionNumber, dbInfo.version) <= 0, String.Format("Call '{0}' made out of version. It was added in version '{1}' but called in version '{2}'", call, callVersionNumber, dbInfo.version)); } } private List ExtractApiCallList(AsyncAction myAction) { RbacMethodList rbacMethods = new RbacMethodList(); RbacCollectorProxy.GetProxy(rbacMethods); Session session = new Session(RbacCollectorProxy.GetProxy(rbacMethods), myAction.Connection); myAction.RunExternal(session); return rbacMethods.ConvertAll(c => c.Method.ToLower().Replace('.', '_').Replace("async_", "")); } private string TestResource(string name) { return Path.Combine(Directory.GetCurrentDirectory(), "TestResources", name); } #endregion } }