[System.Configuration]: SaveMode and UserLevel.
authorMartin Baulig <martin.baulig@xamarin.com>
Fri, 21 Dec 2012 09:05:00 +0000 (10:05 +0100)
committerMartin Baulig <martin.baulig@xamarin.com>
Fri, 21 Dec 2012 09:20:18 +0000 (10:20 +0100)
* Configuration.Save() and SaveAs() now honors the
  `ConfigurationSaveMode' argument.

* Check whether the file has been modified prior to saving.

* Fix ConfigurationUserLevel inheritance.

* Implement ConfigurationElement.IsModified().

* New ConfigurationSaveTest.cs test:

  - load a custom section using different parent configuration
    files using ExeConfigurationFileMap and different
    ConfigurationUserLevel values.

  - save configuration using different ConfigurationSaveMode
    arguments and different inherited parent configurations.

* ExeConfigurationFileMapTest.cs: Add error checks for missing
  required filenames.

* New TestUtil.cs helper class:

  - RunWithTempFile() and RunWithTempFiles() helper functions.
  - DotNetVersion, ThisDllName and ThisConfigFileName properties,
    moving all the #ifdef's into one central place.

15 files changed:
mcs/class/System.Configuration/System.Configuration/ConfigInfo.cs
mcs/class/System.Configuration/System.Configuration/Configuration.cs
mcs/class/System.Configuration/System.Configuration/ConfigurationElement.cs
mcs/class/System.Configuration/System.Configuration/ConfigurationElementCollection.cs
mcs/class/System.Configuration/System.Configuration/ConfigurationManager.cs
mcs/class/System.Configuration/System.Configuration/ConfigurationSection.cs
mcs/class/System.Configuration/System.Configuration/InternalConfigurationHost.cs
mcs/class/System.Configuration/System.Configuration/SectionGroupInfo.cs
mcs/class/System.Configuration/System.Configuration/SectionInfo.cs
mcs/class/System.Configuration/System.Configuration_test.dll.sources
mcs/class/System.Configuration/Test/System.Configuration/ConfigurationManagerTest.cs
mcs/class/System.Configuration/Test/System.Configuration/ConfigurationSaveTest.cs [new file with mode: 0644]
mcs/class/System.Configuration/Test/System.Configuration/ExeConfigurationFileMapTest.cs
mcs/class/System.Configuration/Test/Util/TestLabel.cs [new file with mode: 0644]
mcs/class/System.Configuration/Test/Util/TestUtil.cs [new file with mode: 0644]

index 3ae314412290f21b76b6305462b8892f11a37634..d1e192510be67eae113c7e015cd69a9217d7ccd5 100644 (file)
@@ -83,6 +83,9 @@ namespace System.Configuration {
                public abstract void WriteData (Configuration config, XmlWriter writer, ConfigurationSaveMode mode);
                
                internal abstract void Merge (ConfigInfo data);
+
+               internal abstract bool HasValues (Configuration config, ConfigurationSaveMode mode);
+               internal abstract void ResetModified (Configuration config);
        }
 }
 
index a3f8dc924f43c9c479e4b3fd578cc0e5025d0ddd..2da304b6c749a012957cf64f50c15331ee39427d 100644 (file)
@@ -391,6 +391,11 @@ namespace System.Configuration {
                
                public void Save (ConfigurationSaveMode mode, bool forceUpdateAll)
                {
+                       if (!forceUpdateAll && (mode != ConfigurationSaveMode.Full) && !HasValues (mode)) {
+                               ResetModified ();
+                               return;
+                       }
+
                        ConfigurationSaveEventHandler saveStart = SaveStart;
                        ConfigurationSaveEventHandler saveEnd = SaveEnd;
                        
@@ -427,6 +432,11 @@ namespace System.Configuration {
                [MonoInternalNote ("Detect if file has changed")]
                public void SaveAs (string filename, ConfigurationSaveMode mode, bool forceUpdateAll)
                {
+                       if (!forceUpdateAll && (mode != ConfigurationSaveMode.Full) && !HasValues (mode)) {
+                               ResetModified ();
+                               return;
+                       }
+                       
                        string dir = Path.GetDirectoryName (Path.GetFullPath (filename));
                        if (!Directory.Exists (dir))
                                Directory.CreateDirectory (dir);
@@ -464,6 +474,7 @@ namespace System.Configuration {
                                
                                SaveData (tw, mode, forceUpdateAll);
                                tw.WriteEndElement ();
+                               ResetModified ();
                        }
                        finally {
                                tw.Flush ();
@@ -475,6 +486,29 @@ namespace System.Configuration {
                {
                        rootGroup.WriteRootData (tw, this, mode);
                }
+
+               bool HasValues (ConfigurationSaveMode mode)
+               {
+                       foreach (ConfigurationLocation loc in Locations) {
+                               if (loc.OpenedConfiguration == null)
+                                       continue;
+                               if (loc.OpenedConfiguration.HasValues (mode))
+                                       return true;
+                       }
+
+                       return rootGroup.HasValues (this, mode);
+               }
+
+               void ResetModified ()
+               {
+                       foreach (ConfigurationLocation loc in Locations) {
+                               if (loc.OpenedConfiguration == null)
+                                       continue;
+                               loc.OpenedConfiguration.ResetModified ();
+                       }
+                       
+                       rootGroup.ResetModified (this);
+               }
                
                bool Load ()
                {
@@ -493,6 +527,7 @@ namespace System.Configuration {
                        using (XmlTextReader reader = new ConfigXmlTextReader (stream, streamName)) {
                                ReadConfigFile (reader, streamName);
                        }
+                       ResetModified ();
                        return true;
                }
 
index 0fe992c6d4a7dbdc609b64f12bdc7f1bd7cab38b..9073710240a43dc5ab1d73fa72c84807092ce0f8 100644 (file)
@@ -4,6 +4,7 @@
 // Authors:
 //     Duncan Mak (duncan@ximian.com)
 //     Lluis Sanchez Gual (lluis@novell.com)
+//     Martin Baulig <martin.baulig@xamarin.com>
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -25,6 +26,7 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
 //
 
 #if NET_2_0
@@ -277,15 +279,6 @@ namespace System.Configuration
                        return code;
                }
 
-               internal virtual bool HasValues ()
-               {
-                       foreach (PropertyInformation pi in ElementInformation.Properties)
-                               if (pi.ValueOrigin != PropertyValueOrigin.Default)
-                                       return true;
-                       
-                       return false;
-               }
-
                internal virtual bool HasLocalModifications ()
                {
                        foreach (PropertyInformation pi in ElementInformation.Properties)
@@ -447,6 +440,20 @@ namespace System.Configuration
 
                protected internal virtual bool IsModified ()
                {
+                       if (modified)
+                               return true;
+
+                       foreach (PropertyInformation prop in ElementInformation.Properties) {
+                               if (!prop.IsElement)
+                                       continue;
+                               var element = prop.Value as ConfigurationElement;
+                               if ((element == null) || !element.IsModified ())
+                                       continue;
+
+                               modified = true;
+                               break;
+                       }
+
                        return modified;
                }
                
@@ -463,7 +470,7 @@ namespace System.Configuration
                protected internal virtual void Reset (ConfigurationElement parentElement)
                {
                        elementPresent = false;
-                       
+
                        if (parentElement != null)
                                ElementInformation.Reset (parentElement.ElementInformation);
                        else
@@ -473,8 +480,14 @@ namespace System.Configuration
                protected internal virtual void ResetModified ()
                {
                        modified = false;
-                       foreach (PropertyInformation p in ElementInformation.Properties)
+
+                       foreach (PropertyInformation p in ElementInformation.Properties) {
                                p.IsModified = false;
+
+                               var element = p.Value as ConfigurationElement;
+                               if (element != null)
+                                       element.ResetModified ();
+                       }
                }
 
                protected internal virtual bool SerializeElement (XmlWriter writer, bool serializeCollectionKey)
@@ -492,13 +505,16 @@ namespace System.Configuration
                        
                        foreach (PropertyInformation prop in ElementInformation.Properties)
                        {
-                               if (prop.IsElement || prop.ValueOrigin == PropertyValueOrigin.Default)
+                               if (prop.IsElement)
                                        continue;
-                               
-                               if (!object.Equals (prop.Value, prop.DefaultValue)) {
-                                       writer.WriteAttributeString (prop.Name, prop.GetStringValue ());
-                                       wroteData = true;
-                               }
+
+                               if (saveContext == null)
+                                       throw new InvalidOperationException ();
+                               if (!saveContext.HasValue (prop))
+                                       continue;
+
+                               writer.WriteAttributeString (prop.Name, prop.GetStringValue ());
+                               wroteData = true;
                        }
                        
                        foreach (PropertyInformation prop in ElementInformation.Properties)
@@ -512,11 +528,13 @@ namespace System.Configuration
                        }
                        return wroteData;
                }
-                               
+
                protected internal virtual bool SerializeToXmlElement (
                                XmlWriter writer, string elementName)
                {
-                       if (!HasValues ())
+                       if (saveContext == null)
+                               throw new InvalidOperationException ();
+                       if (!saveContext.HasValues ())
                                return false;
 
                        if (elementName != null && elementName != "")
@@ -533,7 +551,10 @@ namespace System.Configuration
                {
                        if (parent != null && source.GetType() != parent.GetType())
                                throw new ConfigurationErrorsException ("Can't unmerge two elements of different type");
-                       
+
+                       bool isMinimalOrModified = updateMode == ConfigurationSaveMode.Minimal ||
+                               updateMode == ConfigurationSaveMode.Modified;
+
                        foreach (PropertyInformation prop in source.ElementInformation.Properties)
                        {
                                if (prop.ValueOrigin == PropertyValueOrigin.Default)
@@ -542,27 +563,34 @@ namespace System.Configuration
                                PropertyInformation unmergedProp = ElementInformation.Properties [prop.Name];
                                
                                object sourceValue = prop.Value;
-                               if      (parent == null || !parent.HasValue (prop.Name)) {
+                               if (parent == null || !parent.HasValue (prop.Name)) {
                                        unmergedProp.Value = sourceValue;
                                        continue;
                                }
-                               else if (sourceValue != null) {
-                                       object parentValue = parent [prop.Name];
-                                       if (prop.IsElement) {
-                                               if (parentValue != null) {
-                                                       ConfigurationElement copy = (ConfigurationElement) unmergedProp.Value;
-                                                       copy.Unmerge ((ConfigurationElement) sourceValue, (ConfigurationElement) parentValue, updateMode);
-                                               }
-                                               else
-                                                       unmergedProp.Value = sourceValue;
-                                       }
-                                       else {
-                                               if (!object.Equals (sourceValue, parentValue) || 
-                                                       (updateMode == ConfigurationSaveMode.Full) ||
-                                                       (updateMode == ConfigurationSaveMode.Modified && prop.ValueOrigin == PropertyValueOrigin.SetHere))
-                                                       unmergedProp.Value = sourceValue;
-                                       }
+
+                               if (sourceValue == null)
+                                       continue;
+
+                               object parentValue = parent [prop.Name];
+                               if (!prop.IsElement) {
+                                       if (!object.Equals (sourceValue, parentValue) || 
+                                           (updateMode == ConfigurationSaveMode.Full) ||
+                                           (updateMode == ConfigurationSaveMode.Modified && prop.ValueOrigin == PropertyValueOrigin.SetHere))
+                                               unmergedProp.Value = sourceValue;
+                                       continue;
+                               }
+
+                               var sourceElement = (ConfigurationElement) sourceValue;
+                               if (isMinimalOrModified && !sourceElement.IsModified ())
+                                       continue;
+                               if (parentValue == null) {
+                                       unmergedProp.Value = sourceValue;
+                                       continue;
                                }
+
+                               var parentElement = (ConfigurationElement) parentValue;
+                               ConfigurationElement copy = (ConfigurationElement) unmergedProp.Value;
+                               copy.Unmerge (sourceElement, parentElement, updateMode);
                        }
                }
                
@@ -594,6 +622,150 @@ namespace System.Configuration
                                        String.Format ("Validator does not support type {0}", p.Type));
                        validator.Validate (p.ConvertFromString (value));
                }
+
+               /*
+                * FIXME: LAMESPEC
+                * 
+                * SerializeElement() and SerializeToXmlElement() need to emit different output
+                * based on the ConfigurationSaveMode that's being used.  Unfortunately, neither
+                * of these methods take it as an argument and there seems to be no documented way
+                * how to get it.
+                * 
+                * The parent element is needed because the element could be set to a different
+                * than the default value in a parent configuration file, then set locally to that
+                * same value.  This makes the element appear locally modified (so it's included
+                * with ConfigurationSaveMode.Modified), but it should not be emitted with
+                * ConfigurationSaveMode.Minimal.
+                * 
+                * In theory, we could save it into some private field in Unmerge(), but the
+                * problem is that Unmerge() is kinda expensive and we also need a way of
+                * determining whether or not the configuration has changed in Configuration.Save(),
+                * prior to opening the output file for writing.
+                * 
+                * There are two places from where HasValues() is called:
+                * a) From Configuration.Save() / SaveAs() to check whether the configuration needs
+                *    to be saved.  This check is done prior to opening the file for writing.
+                * b) From SerializeToXmlElement() to check whether to emit the element, using the
+                *    parent and mode values from the cached 'SaveContext'.
+                * 
+                */
+
+               /*
+                * Check whether property 'prop' should be included in the serialized XML
+                * based on the current ConfigurationSaveMode.
+                */
+               internal bool HasValue (ConfigurationElement parent, PropertyInformation prop,
+                                       ConfigurationSaveMode mode)
+               {
+                       if (prop.ValueOrigin == PropertyValueOrigin.Default)
+                               return false;
+                       
+                       if (mode == ConfigurationSaveMode.Modified &&
+                           prop.ValueOrigin == PropertyValueOrigin.SetHere && prop.IsModified) {
+                               // Value has been modified locally, so we always emit it
+                               // with ConfigurationSaveMode.Modified.
+                               return true;
+                       }
+
+                       /*
+                        * Ok, now we have to check whether we're different from the inherited
+                        * value - which could either be a value that's set in a parent
+                        * configuration file or the default value.
+                        */
+                       
+                       var hasParentValue = parent != null && parent.HasValue (prop.Name);
+                       var parentOrDefault = hasParentValue ? parent [prop.Name] : prop.DefaultValue;
+                       
+                       if (!prop.IsElement || !hasParentValue)
+                               return !object.Equals (prop.Value, parentOrDefault);
+
+                       /*
+                        * Ok, it's an element that has been set in a parent configuration file.                         * 
+                        * Recursively call HasValues() to check whether it's been locally modified.
+                        */
+                       var element = (ConfigurationElement) prop.Value;
+                       var parentElement = (ConfigurationElement) parentOrDefault;
+                       
+                       return element.HasValues (parentElement, mode);
+               }
+
+               /*
+                * Check whether this element should be included in the serialized XML
+                * based on the current ConfigurationSaveMode.
+                * 
+                * The 'parent' value is needed to determine whether the element currently
+                * has a different value from what's been set in the parent configuration
+                * hierarchy.
+                */
+               internal virtual bool HasValues (ConfigurationElement parent, ConfigurationSaveMode mode)
+               {
+                       if (mode == ConfigurationSaveMode.Full)
+                               return true;
+                       if (modified && (mode == ConfigurationSaveMode.Modified))
+                               return true;
+                       
+                       foreach (PropertyInformation prop in ElementInformation.Properties) {
+                               if (HasValue (parent, prop, mode))
+                                       return true;
+                       }
+                       
+                       return false;
+               }
+
+               /*
+                * Cache the current 'parent' and 'mode' values for later use in SerializeToXmlElement()
+                * and SerializeElement().
+                * 
+                * Make sure to call base when overriding this in a derived class.
+                */
+               internal virtual void PrepareSave (ConfigurationElement parent, ConfigurationSaveMode mode)
+               {
+                       saveContext = new SaveContext (this, parent, mode);
+
+                       foreach (PropertyInformation prop in ElementInformation.Properties)
+                       {
+                               if (!prop.IsElement)
+                                       continue;
+
+                               var elem = (ConfigurationElement)prop.Value;
+                               if (parent == null || !parent.HasValue (prop.Name))
+                                       elem.PrepareSave (null, mode);
+                               else {
+                                       var parentValue = (ConfigurationElement)parent [prop.Name];
+                                       elem.PrepareSave (parentValue, mode);
+                               }
+                       }
+               }
+
+               SaveContext saveContext;
+
+               class SaveContext {
+                       public readonly ConfigurationElement Element;
+                       public readonly ConfigurationElement Parent;
+                       public readonly ConfigurationSaveMode Mode;
+
+                       public SaveContext (ConfigurationElement element, ConfigurationElement parent,
+                                           ConfigurationSaveMode mode)
+                       {
+                               this.Element = element;
+                               this.Parent = parent;
+                               this.Mode = mode;
+                       }
+
+                       public bool HasValues ()
+                       {
+                               if (Mode == ConfigurationSaveMode.Full)
+                                       return true;
+                               return Element.HasValues (Parent, Mode);
+                       }
+
+                       public bool HasValue (PropertyInformation prop)
+                       {
+                               if (Mode == ConfigurationSaveMode.Full)
+                                       return true;
+                               return Element.HasValue (Parent, prop, Mode);
+                       }
+               }
        }
        
        internal class ElementMap
index d998ec9e5c366a69e398f5c102f65ede2bf3a95d..22bc35f1554311967ec0fd4ba8beb36f5ac07498 100644 (file)
@@ -3,8 +3,10 @@
 //
 // Authors:
 //     Tim Coleman (tim@timcoleman.com)
+//     Martin Baulig <martin.baulig@xamarin.com>
 //
 // Copyright (C) Tim Coleman, 2004
+// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
 
 //
 // Permission is hereby granted, free of charge, to any person obtaining
@@ -368,6 +370,17 @@ namespace System.Configuration
 
                protected internal override bool IsModified ()
                {
+                       if (modified)
+                               return true;
+
+                       for (int n=0; n<list.Count; n++) {
+                               ConfigurationElement elem = (ConfigurationElement) list [n];
+                               if (!elem.IsModified ())
+                                       continue;
+                               modified = true;
+                               break;
+                       }
+
                        return modified;
                }
 
@@ -377,9 +390,37 @@ namespace System.Configuration
                        return base.IsReadOnly ();
                }
 
-               internal override bool HasValues ()
+               internal override void PrepareSave (ConfigurationElement parentElement, ConfigurationSaveMode mode)
+               {
+                       var parent = (ConfigurationElementCollection)parentElement;
+                       base.PrepareSave (parentElement, mode);
+
+                       for (int n=0; n<list.Count; n++) {
+                               ConfigurationElement elem = (ConfigurationElement) list [n];
+                               object key = GetElementKey (elem);
+                               ConfigurationElement pitem = parent != null ? parent.BaseGet (key) as ConfigurationElement : null;
+
+                               elem.PrepareSave (pitem, mode);
+                       }
+               }
+
+               internal override bool HasValues (ConfigurationElement parentElement, ConfigurationSaveMode mode)
                {
-                       return list.Count > 0;
+                       var parent = (ConfigurationElementCollection)parentElement;
+
+                       if (mode == ConfigurationSaveMode.Full)
+                               return list.Count > 0;
+
+                       for (int n=0; n<list.Count; n++) {
+                               ConfigurationElement elem = (ConfigurationElement) list [n];
+                               object key = GetElementKey (elem);
+                               ConfigurationElement pitem = parent != null ? parent.BaseGet (key) as ConfigurationElement : null;
+
+                               if (elem.HasValues (pitem, mode))
+                                       return true;
+                       }
+
+                       return false;
                }
 
                protected internal override void Reset (ConfigurationElement parentElement)
@@ -410,6 +451,10 @@ namespace System.Configuration
                protected internal override void ResetModified ()
                {
                        modified = false;
+                       for (int n=0; n<list.Count; n++) {
+                               ConfigurationElement elem = (ConfigurationElement) list [n];
+                               elem.ResetModified ();
+                       }
                }
 
                [MonoTODO]
@@ -521,14 +566,12 @@ namespace System.Configuration
                                ConfigurationElement sitem = source.BaseGet (n);
                                object key = source.GetElementKey (sitem);
                                ConfigurationElement pitem = parent != null ? parent.BaseGet (key) as ConfigurationElement : null;
+                               ConfigurationElement nitem = CreateNewElementInternal (null);
                                if (pitem != null && updateMode != ConfigurationSaveMode.Full) {
-                                       ConfigurationElement nitem = CreateNewElementInternal (null);
-                                       nitem.Unmerge (sitem, pitem, ConfigurationSaveMode.Minimal);
-                                       if (nitem.HasValues ())
+                                       nitem.Unmerge (sitem, pitem, updateMode);
+                                       if (nitem.HasValues (pitem, updateMode))
                                                BaseAdd (nitem);
-                               }
-                               else {
-                                       ConfigurationElement nitem = CreateNewElementInternal (null);
+                               } else {
                                        nitem.Unmerge (sitem, null, ConfigurationSaveMode.Full);
                                        BaseAdd (nitem);
                                }
index 53e31515c143456f20794a90c455e13e85fa0e35..7835a0b0fda924d5b8089467ca4fcf67a82402ae 100644 (file)
@@ -108,12 +108,12 @@ namespace System.Configuration {
                        case ConfigurationUserLevel.PerUserRoaming:
                                map.RoamingUserConfigFilename = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData), GetAssemblyInfo(calling_assembly));
                                map.RoamingUserConfigFilename = Path.Combine (map.RoamingUserConfigFilename, "user.config");
-                               goto case ConfigurationUserLevel.PerUserRoamingAndLocal;
+                               goto case ConfigurationUserLevel.None;
 
                        case ConfigurationUserLevel.PerUserRoamingAndLocal:
                                map.LocalUserConfigFilename = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), GetAssemblyInfo(calling_assembly));
                                map.LocalUserConfigFilename = Path.Combine (map.LocalUserConfigFilename, "user.config");
-                               break;
+                               goto case ConfigurationUserLevel.PerUserRoaming;
                        }
 
                        return ConfigurationFactory.Create (typeof(ExeConfigurationHost), map, userLevel);
index 84723f85aa5e8ae7698f977578ee6ca21c7e28cd..74d7de303cf723701594ccec9488e487f3d62bb3 100644 (file)
@@ -3,7 +3,8 @@
 //
 // Authors:
 //     Duncan Mak (duncan@ximian.com)
-//  Lluis Sanchez Gual (lluis@novell.com)
+//     Lluis Sanchez Gual (lluis@novell.com)
+//     Martin Baulig <martin.baulig@xamarin.com>
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -25,6 +26,7 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
 //
 
 #if NET_2_0
@@ -221,7 +223,7 @@ namespace System.Configuration
                        SectionInformation.SetRawXml (RawXml);
                        DeserializeElement (new ConfigXmlTextReader (new StringReader (RawXml), path), false);
                }
-               
+
                protected internal virtual string SerializeSection (ConfigurationElement parentElement, string name, ConfigurationSaveMode saveMode)
                {
                        externalDataXml = null;
@@ -232,12 +234,28 @@ namespace System.Configuration
                        }
                        else
                                elem = this;
-                       
+
+                       /*
+                        * FIXME: LAMESPEC
+                        * 
+                        * Cache the current values of 'parentElement' and 'saveMode' for later use in
+                        * ConfigurationElement.SerializeToXmlElement().
+                        * 
+                        */
+                       elem.PrepareSave (parentElement, saveMode);
+                       bool hasValues = elem.HasValues (parentElement, saveMode);
+
                        string ret;                     
                        using (StringWriter sw = new StringWriter ()) {
                                using (XmlTextWriter tw = new XmlTextWriter (sw)) {
                                        tw.Formatting = Formatting.Indented;
-                                       elem.SerializeToXmlElement (tw, name);
+                                       if (hasValues)
+                                               elem.SerializeToXmlElement (tw, name);
+                                       else if ((saveMode == ConfigurationSaveMode.Modified) && elem.IsModified ()) {
+                                               // MS emits an empty section element.
+                                               tw.WriteStartElement (name);
+                                               tw.WriteEndElement ();
+                                       }
                                        tw.Close ();
                                }
                                
index 07ce1da61d55649f9c86a86ee8a146fd99e5b974..f5a4cd18a5345e20983e1f6d95a6875e1e4ca799 100644 (file)
@@ -282,6 +282,28 @@ namespace System.Configuration
                {
                        map = (ExeConfigurationFileMap) hostInitParams [0];
                        level = (ConfigurationUserLevel) hostInitParams [1];
+                       CheckFileMap (level, map);
+               }
+
+               static void CheckFileMap (ConfigurationUserLevel level, ExeConfigurationFileMap map)
+               {
+                       switch (level) {
+                       case ConfigurationUserLevel.None:
+                               if (string.IsNullOrEmpty (map.ExeConfigFilename))
+                                       throw new ArgumentException (
+                                               "The 'ExeConfigFilename' argument cannot be null.");
+                               break;
+                       case ConfigurationUserLevel.PerUserRoamingAndLocal:
+                               if (string.IsNullOrEmpty (map.LocalUserConfigFilename))
+                                       throw new ArgumentException (
+                                               "The 'LocalUserConfigFilename' argument cannot be null.");
+                               goto case ConfigurationUserLevel.PerUserRoaming;
+                       case ConfigurationUserLevel.PerUserRoaming:
+                               if (string.IsNullOrEmpty (map.RoamingUserConfigFilename))
+                                       throw new ArgumentException (
+                                               "The 'RoamingUserConfigFilename' argument cannot be null.");
+                               goto case ConfigurationUserLevel.None;
+                       }
                }
                
                public override string GetStreamName (string configPath)
@@ -312,6 +334,9 @@ namespace System.Configuration
                        if (hostInitConfigurationParams.Length > 1 && 
                            hostInitConfigurationParams [1] is ConfigurationUserLevel)
                                level = (ConfigurationUserLevel) hostInitConfigurationParams [1];
+
+                       CheckFileMap (level, map);
+
                        if (locationSubPath == null)
                                switch (level) {
                                case ConfigurationUserLevel.PerUserRoaming:
@@ -333,7 +358,7 @@ namespace System.Configuration
 
                        if (locationSubPath == "exe" || locationSubPath == null && map.ExeConfigFilename != null) {
                                configPath = "exe";
-                               next = "local";
+                               next = "machine";
                                locationConfigPath = map.ExeConfigFilename;
                        }
                        
@@ -345,7 +370,7 @@ namespace System.Configuration
                        
                        if (locationSubPath == "roaming" && map.RoamingUserConfigFilename != null) {
                                configPath = "roaming";
-                               next = "machine";
+                               next = "exe";
                                locationConfigPath = map.RoamingUserConfigFilename;
                        }
                        
index 5332ef9bc9bb7a51dcfbf215d2dfb755bf2a1e88..3bb64d17db203d55de9ff23a24d7068e8913e099 100644 (file)
@@ -37,6 +37,7 @@ namespace System.Configuration
 {
        internal class SectionGroupInfo: ConfigInfo
        {
+               bool modified;
                ConfigInfoCollection sections;
                ConfigInfoCollection groups;
                static ConfigInfoCollection emptyList = new ConfigInfoCollection ();
@@ -54,6 +55,7 @@ namespace System.Configuration
                
                public void AddChild (ConfigInfo data)
                {
+                       modified = true;
                        data.Parent = this;
                        if (data is SectionInfo) {
                                if (sections == null) sections = new ConfigInfoCollection ();
@@ -67,6 +69,7 @@ namespace System.Configuration
                
                public void Clear ()
                {
+                       modified = true;
                        if (sections != null) sections.Clear ();
                        if (groups != null) groups.Clear ();
                }
@@ -79,6 +82,7 @@ namespace System.Configuration
                
                public void RemoveChild (string name)
                {
+                       modified = true;
                        if (sections != null)
                                sections.Remove (name);
                        if (groups != null)
@@ -396,6 +400,33 @@ namespace System.Configuration
                                }
                        }
                }
+
+               internal override bool HasValues (Configuration config, ConfigurationSaveMode mode)
+               {
+                       if (modified && (mode == ConfigurationSaveMode.Modified))
+                               return true;
+
+                       foreach (ConfigInfoCollection col in new object[] { Sections, Groups}) {
+                               foreach (string key in col) {
+                                       ConfigInfo cinfo = col [key];
+                                       if (cinfo.HasValues (config, mode))
+                                               return true;
+                               }
+                       }
+
+                       return false;
+               }
+
+               internal override void ResetModified (Configuration config)
+               {
+                       modified = false;
+                       foreach (ConfigInfoCollection col in new object[] { Sections, Groups}) {
+                               foreach (string key in col) {
+                                       ConfigInfo cinfo = col [key];
+                                       cinfo.ResetModified (config);
+                               }
+                       }
+               }
        }
        
        internal class ConfigInfoCollection : NameObjectCollectionBase
index c9a99aff3b24a771e758dacf1272fc0ab872656a..965745a9db36e872176797bc8be3ac2163ba70b3 100644 (file)
@@ -221,7 +221,7 @@ namespace System.Configuration
                                xml = config.GetSectionXml (this);
                        }
                        
-                       if (xml != null) {
+                       if (!string.IsNullOrEmpty (xml)) {
                                writer.WriteRaw (xml);
 /*                             XmlTextReader tr = new XmlTextReader (new StringReader (xml));
                                writer.WriteNode (tr, true);
@@ -231,6 +231,23 @@ namespace System.Configuration
                
                internal override void Merge (ConfigInfo data)
                {}
+
+               internal override bool HasValues (Configuration config, ConfigurationSaveMode mode)
+               {
+                       var section = config.GetSectionInstance (this, false);
+                       if (section == null)
+                               return false;
+
+                       var parent = config.Parent != null ? config.Parent.GetSectionInstance (this, false) : null;
+                       return section.HasValues (parent, mode);
+               }
+
+               internal override void ResetModified (Configuration config)
+               {
+                       ConfigurationSection section = config.GetSectionInstance (this, false);
+                       if (section != null)
+                               section.ResetModified ();
+               }
        }
 }
 
index c85856013ebc06d291d2a6b78e46619423a86fba..5aa37af01d36879c3d0c8c7cdb4f3e848f0ab9e6 100644 (file)
@@ -9,6 +9,7 @@ System.Configuration/ConfigurationPropertyTest.cs
 System.Configuration/ConfigurationManagerTest.cs
 System.Configuration/ConfigurationSectionGroupTest.cs
 System.Configuration/ConfigurationSectionTest.cs
+System.Configuration/ConfigurationSaveTest.cs
 System.Configuration/ConnectionStringSettingsTest.cs
 System.Configuration/DefaultValidatorTest.cs
 System.Configuration/ExeConfigurationFileMapTest.cs
@@ -32,3 +33,5 @@ System.Configuration/TypeNameConverterTest.cs
 System.Configuration/WhiteSpaceTrimStringConverterTest.cs
 System.Configuration.Provider/ProviderBaseTest.cs
 System.Configuration.Provider/ProviderCollectionTest.cs
+Util/TestLabel.cs
+Util/TestUtil.cs
index d47ce4de9121f749ace9c910567c1106a418a204..f1a383110b2926d0fa2041a89d79b5b8acb270ce 100644 (file)
@@ -39,6 +39,8 @@ using SysConfig = System.Configuration.Configuration;
 using System.Runtime.InteropServices;
 
 namespace MonoTests.System.Configuration {
+       using Util;
+
        [TestFixture]
        public class ConfigurationManagerTest
        {
@@ -62,18 +64,6 @@ namespace MonoTests.System.Configuration {
                                Directory.Delete (tempFolder, true);
                }
                
-               static string DotNetVersion {
-                       get {
-#if NET_4_5
-                               return "net_4_5";
-#elif NET_4_0
-                               return "net_4_0";
-#else
-                               return "net_2_0";
-#endif
-                       }
-               }
-
                [Test] // OpenExeConfiguration (ConfigurationUserLevel)
                [Category ("NotWorking")] // bug #323622
                public void OpenExeConfiguration1_Remote ()
@@ -155,11 +145,7 @@ namespace MonoTests.System.Configuration {
 
                        Console.WriteLine("application config path: {0}", config.FilePath);
                        FileInfo fi = new FileInfo (config.FilePath);
-#if TARGET_JVM
-                       Assert.AreEqual ("nunit-console.jar.config", fi.Name);
-#else
-                       Assert.AreEqual ("System.Configuration_test_" + DotNetVersion + ".dll.config", fi.Name);
-#endif
+                       Assert.AreEqual (TestUtil.ThisConfigFileName, fi.Name);
                }
 
                [Test]
@@ -271,7 +257,7 @@ namespace MonoTests.System.Configuration {
                public void exePath_UserLevelNone ()
                {
                        string basedir = AppDomain.CurrentDomain.BaseDirectory;
-                       string name = "System.Configuration_test_" + DotNetVersion + ".dll";
+                       string name = TestUtil.ThisDllName;
                        SysConfig config = ConfigurationManager.OpenExeConfiguration (name);
                        Assert.AreEqual (Path.Combine (basedir, name + ".config"), config.FilePath);
                }
@@ -400,12 +386,12 @@ namespace MonoTests.System.Configuration {
                [Test]
                public void exePath_UserLevelNone_null ()
                {
-                       SysConfig config = ConfigurationManager.OpenExeConfiguration (null);
 #if false
+                       SysConfig config = ConfigurationManager.OpenExeConfiguration (null);
                        Console.WriteLine("null exe application config path: {0}", config.FilePath);    
 
                        FileInfo fi = new FileInfo (config.FilePath);
-                       Assert.AreEqual ("System.Configuration_test_" + DotNetVersion + ".dll.config", fi.Name);
+                       Assert.AreEqual (TestUtil.ThisConfigFileName, fi.Name);
 #endif
                }
 
@@ -414,14 +400,10 @@ namespace MonoTests.System.Configuration {
                public void mapped_ExeConfiguration_null ()
                {
                        SysConfig config = ConfigurationManager.OpenMappedExeConfiguration(null, ConfigurationUserLevel.None);
-                       Console.WriteLine("null mapped application config path: {0}", config.FilePath); 
+                       Console.WriteLine("null mapped application config path: {0}", config.FilePath);
 
                        FileInfo fi = new FileInfo (config.FilePath);
-#if TARGET_JVM
-                       Assert.AreEqual("System.Configuration.Test20.jar.config", fi.Name);
-#else
-                       Assert.AreEqual ("System.Configuration_test_" + DotNetVersion + ".dll.config", fi.Name);
-#endif
+                       Assert.AreEqual (TestUtil.ThisConfigFileName, fi.Name);
                }
 
                [Test]
diff --git a/mcs/class/System.Configuration/Test/System.Configuration/ConfigurationSaveTest.cs b/mcs/class/System.Configuration/Test/System.Configuration/ConfigurationSaveTest.cs
new file mode 100644 (file)
index 0000000..52f3e57
--- /dev/null
@@ -0,0 +1,695 @@
+//
+// ConfigurationSaveTest.cs
+//
+// Author:
+//       Martin Baulig <martin.baulig@xamarin.com>
+//
+// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Schema;
+using System.Xml.XPath;
+using System.Text;
+using System.Reflection;
+using System.Globalization;
+using System.Configuration;
+using System.Collections.Generic;
+using SysConfig = System.Configuration.Configuration;
+
+using NUnit.Framework;
+using NUnit.Framework.Constraints;
+using NUnit.Framework.SyntaxHelpers;
+
+namespace MonoTests.System.Configuration {
+       using Util;
+
+       [TestFixture]
+       public class ConfigurationSaveTest {
+
+               #region Test Framework
+
+               public abstract class ConfigProvider {
+                       public void Create (string filename)
+                       {
+                               if (File.Exists (filename))
+                                       File.Delete (filename);
+                               
+                               var settings = new XmlWriterSettings ();
+                               settings.Indent = true;
+                               
+                               using (var writer = XmlTextWriter.Create (filename, settings)) {
+                                       writer.WriteStartElement ("configuration");
+                                       WriteXml (writer);
+                                       writer.WriteEndElement ();
+                               }
+                       }
+
+                       public abstract UserLevel Level {
+                               get;
+                       }
+                       
+                       public enum UserLevel {
+                               MachineAndExe,
+                               RoamingAndExe
+                       }
+                       
+                       public virtual SysConfig OpenConfig (string parentFile, string configFile)
+                       {
+                               ConfigurationUserLevel level;
+                               var map = new ExeConfigurationFileMap ();
+                               switch (Level) {
+                               case UserLevel.MachineAndExe:
+                                       map.ExeConfigFilename = configFile;
+                                       map.MachineConfigFilename = parentFile;
+                                       level = ConfigurationUserLevel.None;
+                                       break;
+                               case UserLevel.RoamingAndExe:
+                                       map.RoamingUserConfigFilename = configFile;
+                                       map.ExeConfigFilename = parentFile;
+                                       level = ConfigurationUserLevel.PerUserRoaming;
+                                       break;
+                               default:
+                                       throw new InvalidOperationException ();
+                               }
+                               
+                               return ConfigurationManager.OpenMappedExeConfiguration (map, level);
+                       }
+                       
+                       protected abstract void WriteXml (XmlWriter writer);
+               }
+
+               public abstract class MachineConfigProvider : ConfigProvider {
+                       protected override void WriteXml (XmlWriter writer)
+                       {
+                               writer.WriteStartElement ("configSections");
+                               WriteSections (writer);
+                               writer.WriteEndElement ();
+                               WriteValues (writer);
+                       }
+
+                       public override UserLevel Level {
+                               get { return UserLevel.MachineAndExe; }
+                       }
+                       
+                       protected abstract void WriteSections (XmlWriter writer);
+
+                       protected abstract void WriteValues (XmlWriter writer);
+               }
+
+               class DefaultMachineConfig : MachineConfigProvider {
+                       protected override void WriteSections (XmlWriter writer)
+                       {
+                               writer.WriteStartElement ("section");
+                               writer.WriteAttributeString ("name", "my");
+                               writer.WriteAttributeString ("type", typeof (MySection).AssemblyQualifiedName);
+                               writer.WriteAttributeString ("allowLocation", "true");
+                               writer.WriteAttributeString ("allowDefinition", "Everywhere");
+                               writer.WriteAttributeString ("allowExeDefinition", "MachineToRoamingUser");
+                               writer.WriteAttributeString ("restartOnExternalChanges", "true");
+                               writer.WriteAttributeString ("requirePermission", "true");
+                               writer.WriteEndElement ();
+                       }
+
+                       internal static void WriteConfigSections (XmlWriter writer)
+                       {
+                               var provider = new DefaultMachineConfig ();
+                               writer.WriteStartElement ("configSections");
+                               provider.WriteSections (writer);
+                               writer.WriteEndElement ();
+                       }
+
+                       protected override void WriteValues (XmlWriter writer)
+                       {
+                               writer.WriteStartElement ("my");
+                               writer.WriteEndElement ();
+                       }
+               }
+
+               abstract class ParentProvider : ConfigProvider {
+                       protected override void WriteXml (XmlWriter writer)
+                       {
+                               DefaultMachineConfig.WriteConfigSections (writer);
+                               writer.WriteStartElement ("my");
+                               writer.WriteStartElement ("test");
+                               writer.WriteAttributeString ("Hello", "29");
+                               writer.WriteEndElement ();
+                               writer.WriteEndElement ();
+                       }
+               }
+
+               class RoamingAndExe : ParentProvider {
+                       public override UserLevel Level {
+                               get { return UserLevel.RoamingAndExe; }
+                       }
+               }
+
+               public delegate void TestFunction (SysConfig config, TestLabel label);
+               public delegate void XmlCheckFunction (XPathNavigator nav, TestLabel label);
+
+               public static void Run (string name, TestFunction func)
+               {
+                       var label = new TestLabel (name);
+
+                       TestUtil.RunWithTempFile (filename => {
+                               var fileMap = new ExeConfigurationFileMap ();
+                               fileMap.ExeConfigFilename = filename;
+                               var config = ConfigurationManager.OpenMappedExeConfiguration (
+                                       fileMap, ConfigurationUserLevel.None);
+                               
+                               func (config, label);
+                       });
+               }
+
+               public static void Run<TConfig> (string name, TestFunction func)
+                       where TConfig : ConfigProvider, new ()
+               {
+                       Run<TConfig> (new TestLabel (name), func, null);
+               }
+
+               public static void Run<TConfig> (TestLabel label, TestFunction func)
+                       where TConfig : ConfigProvider, new ()
+               {
+                       Run<TConfig> (label, func, null);
+               }
+
+               public static void Run<TConfig> (
+                       string name, TestFunction func, XmlCheckFunction check)
+                       where TConfig : ConfigProvider, new ()
+               {
+                       Run<TConfig> (new TestLabel (name), func, check);
+               }
+
+               public static void Run<TConfig> (
+                       TestLabel label, TestFunction func, XmlCheckFunction check)
+                       where TConfig : ConfigProvider, new ()
+               {
+                       TestUtil.RunWithTempFiles ((parent,filename) => {
+                               var provider = new TConfig ();
+                               provider.Create (parent);
+
+                               Assert.That (File.Exists (filename), Is.False);
+
+                               var config = provider.OpenConfig (parent, filename);
+
+                               Assert.That (File.Exists (filename), Is.False);
+
+                               try {
+                                       label.EnterScope ("config");
+                                       func (config, label);
+                               } finally {
+                                       label.LeaveScope ();
+                               }
+
+                               if (check == null)
+                                       return;
+
+                               var xml = new XmlDocument ();
+                               xml.Load (filename);
+
+                               var nav = xml.CreateNavigator ().SelectSingleNode ("/configuration");
+                               try {
+                                       label.EnterScope ("xml");
+                                       check (nav, label);
+                               } finally {
+                                       label.LeaveScope ();
+                               }
+                       });
+               }
+
+               #endregion
+
+               #region Assertion Helpers
+
+               static void AssertNotModified (MySection my, TestLabel label)
+               {
+                       label.EnterScope ("modified");
+                       Assert.That (my, Is.Not.Null, label.Get ());
+                       Assert.That (my.IsModified, Is.False, label.Get ());
+                       Assert.That (my.List, Is.Not.Null, label.Get ());
+                       Assert.That (my.List.Collection.Count, Is.EqualTo (0), label.Get ());
+                       Assert.That (my.List.IsModified, Is.False, label.Get ());
+                       label.LeaveScope ();
+               }
+
+               static void AssertListElement (XPathNavigator nav, TestLabel label)
+               {
+                       Assert.That (nav.HasChildren, Is.True, label.Get ());
+                       var iter = nav.SelectChildren (XPathNodeType.Element);
+                       
+                       Assert.That (iter.Count, Is.EqualTo (1), label.Get ());
+                       Assert.That (iter.MoveNext (), Is.True, label.Get ());
+                       
+                       var my = iter.Current;
+                       label.EnterScope ("my");
+                       Assert.That (my.Name, Is.EqualTo ("my"), label.Get ());
+                       Assert.That (my.HasAttributes, Is.False, label.Get ());
+                       
+                       label.EnterScope ("children");
+                       Assert.That (my.HasChildren, Is.True, label.Get ());
+                       var iter2 = my.SelectChildren (XPathNodeType.Element);
+                       Assert.That (iter2.Count, Is.EqualTo (1), label.Get ());
+                       Assert.That (iter2.MoveNext (), Is.True, label.Get ());
+                       
+                       var test = iter2.Current;
+                       label.EnterScope ("test");
+                       Assert.That (test.Name, Is.EqualTo ("test"), label.Get ());
+                       Assert.That (test.HasChildren, Is.False, label.Get ());
+                       Assert.That (test.HasAttributes, Is.True, label.Get ());
+                       
+                       var attr = test.GetAttribute ("Hello", string.Empty);
+                       Assert.That (attr, Is.EqualTo ("29"), label.Get ());
+                       label.LeaveScope ();
+                       label.LeaveScope ();
+                       label.LeaveScope ();
+               }
+               
+               #endregion
+
+               #region Tests
+
+               [Test]
+               public void DefaultValues ()
+               {
+                       Run<DefaultMachineConfig> ("DefaultValues", (config,label) => {
+                               var my = config.Sections ["my"] as MySection;
+
+                               AssertNotModified (my, label);
+
+                               label.EnterScope ("file");
+                               Assert.That (File.Exists (config.FilePath), Is.False, label.Get ());
+
+                               config.Save (ConfigurationSaveMode.Minimal);
+                               Assert.That (File.Exists (config.FilePath), Is.False, label.Get ());
+                               label.LeaveScope ();
+                       });
+               }
+
+               [Test]
+               public void AddDefaultListElement ()
+               {
+                       Run<DefaultMachineConfig> ("AddDefaultListElement", (config,label) => {
+                               var my = config.Sections ["my"] as MySection;
+                               
+                               AssertNotModified (my, label);
+                               
+                               label.EnterScope ("add");
+                               var element = my.List.Collection.AddElement ();
+                               Assert.That (my.IsModified, Is.True, label.Get ());
+                               Assert.That (my.List.IsModified, Is.True, label.Get ());
+                               Assert.That (my.List.Collection.IsModified, Is.True, label.Get ());
+                               Assert.That (element.IsModified, Is.False, label.Get ());
+                               label.LeaveScope ();
+                               
+                               config.Save (ConfigurationSaveMode.Minimal);
+                               Assert.That (File.Exists (config.FilePath), Is.False, label.Get ());
+                       });
+               }
+               
+               [Test]
+               public void AddDefaultListElement2 ()
+               {
+                       Run<DefaultMachineConfig> ("AddDefaultListElement2", (config,label) => {
+                               var my = config.Sections ["my"] as MySection;
+                               
+                               AssertNotModified (my, label);
+                               
+                               label.EnterScope ("add");
+                               var element = my.List.Collection.AddElement ();
+                               Assert.That (my.IsModified, Is.True, label.Get ());
+                               Assert.That (my.List.IsModified, Is.True, label.Get ());
+                               Assert.That (my.List.Collection.IsModified, Is.True, label.Get ());
+                               Assert.That (element.IsModified, Is.False, label.Get ());
+                               label.LeaveScope ();
+                               
+                               config.Save (ConfigurationSaveMode.Modified);
+                               Assert.That (File.Exists (config.FilePath), Is.True, label.Get ());
+                       }, (nav,label) => {
+                               Assert.That (nav.HasChildren, Is.True, label.Get ());
+                               var iter = nav.SelectChildren (XPathNodeType.Element);
+                               
+                               Assert.That (iter.Count, Is.EqualTo (1), label.Get ());
+                               Assert.That (iter.MoveNext (), Is.True, label.Get ());
+                               
+                               var my = iter.Current;
+                               label.EnterScope ("my");
+                               Assert.That (my.Name, Is.EqualTo ("my"), label.Get ());
+                               Assert.That (my.HasAttributes, Is.False, label.Get ());
+                               Assert.That (my.HasChildren, Is.False, label.Get ());
+                               label.LeaveScope ();
+                       });
+               }
+
+               [Test]
+               public void AddDefaultListElement3 ()
+               {
+                       Run<DefaultMachineConfig> ("AddDefaultListElement3", (config,label) => {
+                               var my = config.Sections ["my"] as MySection;
+
+                               AssertNotModified (my, label);
+
+                               label.EnterScope ("add");
+                               var element = my.List.Collection.AddElement ();
+                               Assert.That (my.IsModified, Is.True, label.Get ());
+                               Assert.That (my.List.IsModified, Is.True, label.Get ());
+                               Assert.That (my.List.Collection.IsModified, Is.True, label.Get ());
+                               Assert.That (element.IsModified, Is.False, label.Get ());
+                               label.LeaveScope ();
+                               
+                               config.Save (ConfigurationSaveMode.Full);
+                               Assert.That (File.Exists (config.FilePath), Is.True, label.Get ());
+                       }, (nav,label) => {
+                               Assert.That (nav.HasChildren, Is.True, label.Get ());
+                               var iter = nav.SelectChildren (XPathNodeType.Element);
+                               
+                               Assert.That (iter.Count, Is.EqualTo (1), label.Get ());
+                               Assert.That (iter.MoveNext (), Is.True, label.Get ());
+                               
+                               var my = iter.Current;
+                               label.EnterScope ("my");
+                               Assert.That (my.Name, Is.EqualTo ("my"), label.Get ());
+                               Assert.That (my.HasAttributes, Is.False, label.Get ());
+                               
+                               label.EnterScope ("children");
+                               Assert.That (my.HasChildren, Is.True, label.Get ());
+                               var iter2 = my.SelectChildren (XPathNodeType.Element);
+                               Assert.That (iter2.Count, Is.EqualTo (2), label.Get ());
+
+                               label.EnterScope ("list");
+                               var iter3 = my.Select ("list/*");
+                               Assert.That (iter3.Count, Is.EqualTo (1), label.Get ());
+                               Assert.That (iter3.MoveNext (), Is.True, label.Get ());
+                               var collection = iter3.Current;
+                               Assert.That (collection.Name, Is.EqualTo ("collection"), label.Get ());
+                               Assert.That (collection.HasChildren, Is.False, label.Get ());
+                               Assert.That (collection.HasAttributes, Is.True, label.Get ());
+                               var hello = collection.GetAttribute ("Hello", string.Empty);
+                               Assert.That (hello, Is.EqualTo ("8"), label.Get ());
+                               var world = collection.GetAttribute ("World", string.Empty);
+                               Assert.That (world, Is.EqualTo ("0"), label.Get ());
+                               label.LeaveScope ();
+
+                               label.EnterScope ("test");
+                               var iter4 = my.Select ("test");
+                               Assert.That (iter4.Count, Is.EqualTo (1), label.Get ());
+                               Assert.That (iter4.MoveNext (), Is.True, label.Get ());
+                               var test = iter4.Current;
+                               Assert.That (test.Name, Is.EqualTo ("test"), label.Get ());
+                               Assert.That (test.HasChildren, Is.False, label.Get ());
+                               Assert.That (test.HasAttributes, Is.True, label.Get ());
+                               
+                               var hello2 = test.GetAttribute ("Hello", string.Empty);
+                               Assert.That (hello2, Is.EqualTo ("8"), label.Get ());
+                               var world2 = test.GetAttribute ("World", string.Empty);
+                               Assert.That (world2, Is.EqualTo ("0"), label.Get ());
+                               label.LeaveScope ();
+                               label.LeaveScope ();
+                               label.LeaveScope ();
+                       });
+               }
+
+               [Test]
+               public void AddListElement ()
+               {
+                       Run<DefaultMachineConfig> ("AddListElement", (config,label) => {
+                               var my = config.Sections ["my"] as MySection;
+                               
+                               AssertNotModified (my, label);
+                               
+                               my.Test.Hello = 29;
+                               
+                               label.EnterScope ("file");
+                               Assert.That (File.Exists (config.FilePath), Is.False, label.Get ());
+                               
+                               config.Save (ConfigurationSaveMode.Minimal);
+                               Assert.That (File.Exists (config.FilePath), Is.True, label.Get ());
+                               label.LeaveScope ();
+                       }, (nav,label) => {
+                               AssertListElement (nav, label);
+                       });
+               }
+               
+               [Test]
+               public void NotModifiedAfterSave ()
+               {
+                       Run<DefaultMachineConfig> ("NotModifiedAfterSave", (config,label) => {
+                               var my = config.Sections ["my"] as MySection;
+
+                               AssertNotModified (my, label);
+
+                               label.EnterScope ("add");
+                               var element = my.List.Collection.AddElement ();
+                               Assert.That (my.IsModified, Is.True, label.Get ());
+                               Assert.That (my.List.IsModified, Is.True, label.Get ());
+                               Assert.That (my.List.Collection.IsModified, Is.True, label.Get ());
+                               Assert.That (element.IsModified, Is.False, label.Get ());
+                               label.LeaveScope ();
+
+                               label.EnterScope ("1st-save");
+                               config.Save (ConfigurationSaveMode.Minimal);
+                               Assert.That (File.Exists (config.FilePath), Is.False, label.Get ());
+                               config.Save (ConfigurationSaveMode.Modified);
+                               Assert.That (File.Exists (config.FilePath), Is.False, label.Get ());
+                               label.LeaveScope ();
+
+                               label.EnterScope ("modify");
+                               element.Hello = 12;
+                               Assert.That (my.IsModified, Is.True, label.Get ());
+                               Assert.That (my.List.IsModified, Is.True, label.Get ());
+                               Assert.That (my.List.Collection.IsModified, Is.True, label.Get ());
+                               Assert.That (element.IsModified, Is.True, label.Get ());
+                               label.LeaveScope ();
+
+                               label.EnterScope ("2nd-save");
+                               config.Save (ConfigurationSaveMode.Modified);
+                               Assert.That (File.Exists (config.FilePath), Is.True, label.Get ());
+
+                               Assert.That (my.IsModified, Is.False, label.Get ());
+                               Assert.That (my.List.IsModified, Is.False, label.Get ());
+                               Assert.That (my.List.Collection.IsModified, Is.False, label.Get ());
+                               Assert.That (element.IsModified, Is.False, label.Get ());
+                               label.LeaveScope (); // 2nd-save
+                       });
+               }
+
+               [Test]
+               public void AddSection ()
+               {
+                       Run ("AddSection", (config,label) => {
+                               Assert.That (config.Sections ["my"], Is.Null, label.Get ());
+
+                               var my = new MySection ();
+                               config.Sections.Add ("my2", my);
+                               config.Save (ConfigurationSaveMode.Full);
+
+                               Assert.That (File.Exists (config.FilePath), Is.True, label.Get ());
+                       });
+               }
+
+               [Test]
+               public void AddElement ()
+               {
+                       Run<DefaultMachineConfig> ("AddElement", (config,label) => {
+                               var my = config.Sections ["my"] as MySection;
+
+                               AssertNotModified (my, label);
+
+                               var element = my.List.DefaultCollection.AddElement ();
+                               element.Hello = 12;
+
+                               config.Save (ConfigurationSaveMode.Modified);
+
+                               label.EnterScope ("file");
+                               Assert.That (File.Exists (config.FilePath), Is.True, "#c2");
+                               label.LeaveScope ();
+                       }, (nav,label) => {
+                               Assert.That (nav.HasChildren, Is.True, label.Get ());
+                               var iter = nav.SelectChildren (XPathNodeType.Element);
+                               
+                               Assert.That (iter.Count, Is.EqualTo (1), label.Get ());
+                               Assert.That (iter.MoveNext (), Is.True, label.Get ());
+                               
+                               var my = iter.Current;
+                               label.EnterScope ("my");
+                               Assert.That (my.Name, Is.EqualTo ("my"), label.Get ());
+                               Assert.That (my.HasAttributes, Is.False, label.Get ());
+                               Assert.That (my.HasChildren, Is.True, label.Get ());
+
+                               label.EnterScope ("children");
+                               var iter2 = my.SelectChildren (XPathNodeType.Element);
+                               Assert.That (iter2.Count, Is.EqualTo (1), label.Get ());
+                               Assert.That (iter2.MoveNext (), Is.True, label.Get ());
+
+                               var list = iter2.Current;
+                               label.EnterScope ("list");
+                               Assert.That (list.Name, Is.EqualTo ("list"), label.Get ());
+                               Assert.That (list.HasChildren, Is.False, label.Get ());
+                               Assert.That (list.HasAttributes, Is.True, label.Get ());
+
+                               var attr = list.GetAttribute ("Hello", string.Empty);
+                               Assert.That (attr, Is.EqualTo ("12"), label.Get ());
+                               label.LeaveScope ();
+                               label.LeaveScope ();
+                               label.LeaveScope ();
+                       });
+               }
+
+               [Test]
+               public void ModifyListElement ()
+               {
+                       Run<RoamingAndExe> ("ModifyListElement", (config,label) => {
+                               var my = config.Sections ["my"] as MySection;
+
+                               AssertNotModified (my, label);
+                               
+                               my.Test.Hello = 29;
+
+                               label.EnterScope ("file");
+                               Assert.That (File.Exists (config.FilePath), Is.False, label.Get ());
+                               
+                               config.Save (ConfigurationSaveMode.Minimal);
+                               Assert.That (File.Exists (config.FilePath), Is.False, label.Get ());
+                               label.LeaveScope ();
+                       });
+               }
+
+               [Test]
+               public void ModifyListElement2 ()
+               {
+                       Run<RoamingAndExe> ("ModifyListElement2", (config,label) => {
+                               var my = config.Sections ["my"] as MySection;
+
+                               AssertNotModified (my, label);
+                               
+                               my.Test.Hello = 29;
+                               
+                               label.EnterScope ("file");
+                               Assert.That (File.Exists (config.FilePath), Is.False, label.Get ());
+                               
+                               config.Save (ConfigurationSaveMode.Modified);
+                               Assert.That (File.Exists (config.FilePath), Is.True, label.Get ());
+                               label.LeaveScope ();
+                       }, (nav,label) => {
+                               AssertListElement (nav, label);
+                       });
+               }
+
+               #endregion
+
+               #region Configuration Classes
+
+               public class MyElement : ConfigurationElement {
+                       [ConfigurationProperty ("Hello", DefaultValue = 8)]
+                       public int Hello {
+                               get { return (int)base ["Hello"]; }
+                               set { base ["Hello"] = value; }
+                       }
+
+                       [ConfigurationProperty ("World", IsRequired = false)]
+                       public int World {
+                               get { return (int)base ["World"]; }
+                               set { base ["World"] = value; }
+                       }
+
+                       new public bool IsModified {
+                               get { return base.IsModified (); }
+                       }
+               }
+
+               public class MyCollection<T> : ConfigurationElementCollection
+                       where T : ConfigurationElement, new ()
+               {
+                       #region implemented abstract members of ConfigurationElementCollection
+                       protected override ConfigurationElement CreateNewElement ()
+                       {
+                               return new T ();
+                       }
+                       protected override object GetElementKey (ConfigurationElement element)
+                       {
+                               return ((T)element).GetHashCode ();
+                       }
+                       #endregion
+
+                       public override ConfigurationElementCollectionType CollectionType {
+                               get {
+                                       return ConfigurationElementCollectionType.BasicMap;
+                               }
+                       }
+
+                       public T AddElement ()
+                       {
+                               var element = new T ();
+                               BaseAdd (element);
+                               return element;
+                       }
+
+                       public void RemoveElement (T element)
+                       {
+                               BaseRemove (GetElementKey (element));
+                       }
+
+                       public new bool IsModified {
+                               get { return base.IsModified (); }
+                       }
+               }
+
+               public class MyCollectionElement<T> : ConfigurationElement
+                       where T : ConfigurationElement, new ()
+               {
+                       [ConfigurationProperty ("",
+                                               Options = ConfigurationPropertyOptions.IsDefaultCollection,
+                                               IsDefaultCollection = true)]
+                       public MyCollection<T> DefaultCollection {
+                               get { return (MyCollection<T>)this [String.Empty]; }
+                               set { this [String.Empty] = value; }
+                       }
+
+                       [ConfigurationProperty ("collection", Options = ConfigurationPropertyOptions.None)]
+                       public MyCollection<T> Collection {
+                               get { return (MyCollection<T>)this ["collection"]; }
+                               set { this ["collection"] = value; }
+                       }
+
+                       public new bool IsModified {
+                               get { return base.IsModified (); }
+                       }
+               }
+
+               public class MySection : ConfigurationSection {
+                       [ConfigurationProperty ("list", Options = ConfigurationPropertyOptions.None)]
+                       public MyCollectionElement<MyElement> List {
+                               get { return (MyCollectionElement<MyElement>) this ["list"]; }
+                       }
+
+                       [ConfigurationProperty ("test", Options = ConfigurationPropertyOptions.None)]
+                       public MyElement Test {
+                               get { return (MyElement) this ["test"]; }
+                       }
+
+                       new public bool IsModified {
+                               get { return base.IsModified (); }
+                       }
+               }
+
+               #endregion
+       }
+}
+
index d1fbb266693976c68f7f00c92cad4a192653a380..ccc8756a004119f3ca652bb33e843b5d03a8fa84 100644 (file)
@@ -34,6 +34,8 @@ using System.Configuration;
 using NUnit.Framework;
 
 namespace MonoTests.System.Configuration {
+       using Util;
+
        [TestFixture]
        public class ExeConfigurationFileMapTest
        {
@@ -63,6 +65,93 @@ namespace MonoTests.System.Configuration {
                        map.RoamingUserConfigFilename = null;
                        Assert.IsNull (map.RoamingUserConfigFilename, "A8");
                }
+
+               [Test]
+               public void MissingRoamingFilename ()
+               {
+                       TestUtil.RunWithTempFile (filename => {
+                               var map = new ExeConfigurationFileMap ();
+                               map.ExeConfigFilename = filename;
+                               
+                               try {
+                                       ConfigurationManager.OpenMappedExeConfiguration (
+                                               map, ConfigurationUserLevel.PerUserRoaming);
+                                       Assert.Fail ("#1");
+                               } catch (ArgumentException) {
+                                       ;
+                               }
+                       });
+               }
+               
+               [Test]
+               public void MissingRoamingFilename2 ()
+               {
+                       TestUtil.RunWithTempFile (filename => {
+                               var map = new ExeConfigurationFileMap ();
+                               map.LocalUserConfigFilename = filename;
+                               
+                               try {
+                                       ConfigurationManager.OpenMappedExeConfiguration (
+                                               map, ConfigurationUserLevel.PerUserRoamingAndLocal);
+                                       Assert.Fail ("#1");
+                               } catch (ArgumentException) {
+                                       ;
+                               }
+                       });
+               }
+               
+               [Test]
+               public void MissingLocalFilename ()
+               {
+                       TestUtil.RunWithTempFile (filename => {
+                               var map = new ExeConfigurationFileMap ();
+                               map.ExeConfigFilename = filename;
+                               map.RoamingUserConfigFilename = filename;
+                               
+                               try {
+                                       ConfigurationManager.OpenMappedExeConfiguration (
+                                               map, ConfigurationUserLevel.PerUserRoamingAndLocal);
+                                       Assert.Fail ("#1");
+                               } catch (ArgumentException) {
+                                       ;
+                               }
+                       });
+               }
+               
+               [Test]
+               public void MissingExeFilename ()
+               {
+                       TestUtil.RunWithTempFiles ((roaming,local) => {
+                               var map = new ExeConfigurationFileMap ();
+                               map.RoamingUserConfigFilename = roaming;
+                               map.LocalUserConfigFilename = local;
+                               
+                               try {
+                                       ConfigurationManager.OpenMappedExeConfiguration (
+                                               map, ConfigurationUserLevel.PerUserRoamingAndLocal);
+                                       Assert.Fail ("#1");
+                               } catch (ArgumentException) {
+                                       ;
+                               }
+                       });
+               }
+
+               [Test]
+               public void MissingExeFilename2 ()
+               {
+                       TestUtil.RunWithTempFile ((machine) => {
+                               var map = new ExeConfigurationFileMap ();
+                               map.MachineConfigFilename = machine;
+
+                               try {
+                                       ConfigurationManager.OpenMappedExeConfiguration (
+                                               map, ConfigurationUserLevel.None);
+                                       Assert.Fail ("#1");
+                               } catch (ArgumentException) {
+                                       ;
+                               }
+                       });
+               }
        }
 }
 
diff --git a/mcs/class/System.Configuration/Test/Util/TestLabel.cs b/mcs/class/System.Configuration/Test/Util/TestLabel.cs
new file mode 100644 (file)
index 0000000..1e82ad7
--- /dev/null
@@ -0,0 +1,138 @@
+//
+// TestLabel.cs
+//
+// Author:
+//       Martin Baulig <martin.baulig@xamarin.com>
+//
+// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using System.Text;
+using System.Collections.Generic;
+
+namespace MonoTests.System.Configuration.Util {
+
+       public class TestLabel {
+
+               List<Scope> scopes;
+               string delimiter;
+               Style defaultStyle;
+
+               public enum Style {
+                       Letter,
+                       Number,
+                       HexNumer
+               }
+
+               public TestLabel (string prefix)
+                       : this (prefix, ".", Style.Letter)
+               {
+               }
+
+               public TestLabel (string prefix, string delimiter, Style style)
+               {
+                       if ((prefix == null) || (prefix.Equals (string.Empty)))
+                               throw new ArgumentException ("Cannot be null or empty.", "prefix");
+                       if (delimiter == null)
+                               throw new ArgumentNullException ("delimiter");
+
+                       scopes = new List<Scope> ();
+                       scopes.Add (new Scope (prefix, style));
+
+                       this.delimiter = delimiter;
+                       this.defaultStyle = style;
+               }
+
+               class Scope {
+                       public readonly string Text;
+                       public readonly Style Style;
+                       int id;
+
+                       public Scope (string text, Style style)
+                       {
+                               this.Text = text;
+                               this.Style = style;
+                               this.id = 0;
+                       }
+
+                       public int GetID ()
+                       {
+                               return ++id;
+                       }
+               }
+
+               public void EnterScope (string scope)
+               {
+                       scopes.Add (new Scope (scope, defaultStyle));
+               }
+
+               public void LeaveScope ()
+               {
+                       if (scopes.Count <= 1)
+                               throw new InvalidOperationException ();
+                       scopes.RemoveAt (scopes.Count - 1);
+               }
+
+               public string Get ()
+               {
+                       var sb = new StringBuilder ();
+                       for (int i = 0; i < scopes.Count; i++) {
+                               sb.Append (scopes [i].Text);
+                               sb.Append (delimiter);
+                       }
+
+                       var scope = scopes [scopes.Count - 1];
+                       var id = scope.GetID ();
+
+                       switch (scope.Style) {
+                       case Style.Letter:
+                               if (id <= 26)
+                                       sb.Append ((char)('a' + id - 1));
+                               else
+                                       goto case Style.Number;
+                               break;
+
+                       case Style.Number:
+                               sb.Append (id);
+                               break;
+
+                       case Style.HexNumer:
+                               sb.AppendFormat ("{0:x2}", id);
+                               break;
+                       }
+
+                       return sb.ToString ();
+               }
+
+               public override string ToString ()
+               {
+                       var sb = new StringBuilder ();
+                       sb.Append ("[");
+                       for (int i = 0; i < scopes.Count; i++) {
+                               if (i > 0)
+                                       sb.Append (delimiter);
+                               sb.Append (scopes [i].Text);
+                       }
+                       sb.Append ("]");
+                       return sb.ToString ();
+               }
+       }
+}
+
diff --git a/mcs/class/System.Configuration/Test/Util/TestUtil.cs b/mcs/class/System.Configuration/Test/Util/TestUtil.cs
new file mode 100644 (file)
index 0000000..cdadded
--- /dev/null
@@ -0,0 +1,99 @@
+//
+// TestUtil.cs
+//
+// Author:
+//       Martin Baulig <martin.baulig@xamarin.com>
+//
+// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using System.IO;
+using System.Reflection;
+
+namespace MonoTests.System.Configuration.Util {
+       
+       public static class TestUtil {
+
+               public static void RunWithTempFile (Action<string> action)
+               {
+                       var filename = Path.GetTempFileName ();
+                       
+                       try {
+                               File.Delete (filename);
+                               action (filename);
+                       } finally {
+                               if (File.Exists (filename))
+                                       File.Delete (filename);
+                       }
+               }
+
+               // Action<T1,T2> doesn't exist in .NET 2.0
+               public delegate void MyAction<T1,T2> (T1 t1, T2 t2);
+
+               public static void RunWithTempFiles (MyAction<string,string> action)
+               {
+                       var file1 = Path.GetTempFileName ();
+                       var file2 = Path.GetTempFileName ();
+                       
+                       try {
+                               File.Delete (file1);
+                               File.Delete (file2);
+                               action (file1, file2);
+                       } finally {
+                               if (File.Exists (file1))
+                                       File.Delete (file1);
+                               if (File.Exists (file2))
+                                       File.Delete (file2);
+                       }
+               }
+
+               public static string DotNetVersion {
+                       get {
+#if NET_4_5
+                               return "net_4_5";
+#elif NET_4_0
+                               return "net_4_0";
+#else
+                               return "net_2_0";
+#endif
+                       }
+               }
+
+               public static string ThisDllName {
+                       get {
+                               var asm = Assembly.GetCallingAssembly ();
+                               return Path.GetFileName (asm.Location);
+                       }
+               }
+
+               public static string ThisConfigFileName {
+                       get {
+#if TARGET_JVM
+                               return "System.Configuration.Test20.jar.config";
+#else
+                               var asm = Assembly.GetCallingAssembly ();
+                               var exe = Path.GetFileName (asm.Location);
+                               return exe + ".config";
+#endif
+                       }
+               }
+       }
+}
+