Skip to content

Commit

Permalink
Add Android plugin and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Simon committed Oct 10, 2024
1 parent 9ed7852 commit da4111f
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 5 deletions.
1 change: 0 additions & 1 deletion src/cfgnet/config_types/config_type_inferer.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ def get_config_type( # noqa: C901
return ConfigType.COUNT

if bool(re.search(ConfigTypeInferer.regex_name, option_name)):
print(f"Option {option_name} is name.")
return ConfigType.NAME

if bool(re.search(ConfigTypeInferer.regex_pattern, option_name)):
Expand Down
154 changes: 154 additions & 0 deletions src/cfgnet/plugins/concept/android_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# This file is part of the CfgNet module.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/>.

import logging
import os

from typing import Optional
from lxml import etree as ET
from lxml.etree import _Element, _Comment

from cfgnet.config_types.config_types import ConfigType
from cfgnet.network.nodes import (
ArtifactNode,
Node,
OptionNode,
ProjectNode,
ValueNode,
)
from cfgnet.plugins.plugin import Plugin


class AndroidPlugin(Plugin):
def __init__(self):
super().__init__("android")
self.lines = None

def is_responsible(self, abs_file_path: str) -> bool:
file_name = os.path.basename(abs_file_path)
return file_name == "AndroidManifest.xml"

def _parse_config_file(
self,
abs_file_path: str,
rel_file_path: str,
root: Optional[ProjectNode],
) -> ArtifactNode:
artifact = ArtifactNode(
file_path=abs_file_path,
rel_file_path=rel_file_path,
concept_name=self.concept_name,
project_root=root,
)

with open(abs_file_path, "r", encoding="utf-8") as src:
self.lines = src.readlines()

try:
tree = ET.parse(abs_file_path)
root = tree.getroot()

self.parse_tree(root, parent_node=artifact)

except ET.Error as error:
logging.warning(
'Failed to parse xml file "%s" due to %s', rel_file_path, error
)

return artifact

def parse_tree(self, element: _Element, parent_node: Node):
option_name = element.tag
config_type = self.get_config_type(option_name=option_name)
parent_option = OptionNode(
option_name, str(element.sourceline), config_type
)
parent_node.add_child(parent_option)

for attr_name, attr_value in element.attrib.items():
key = attr_name.split("}")[-1]
config_type = self.get_config_type(key)
line_number = self.find_attribute_line(key, attr_value, self.lines)

if not line_number:
line_number = str(element.sourceline)

option = OptionNode(key, line_number, config_type)
parent_option.add_child(option)
value_node = ValueNode(attr_value)
option.add_child(value_node)

for child in element:
if not isinstance(child, _Comment):
self.parse_tree(child, parent_option)

def find_attribute_line(
self, attr_name, attr_value, lines
) -> Optional[str]:
for i in range(len(lines) - 1):
if attr_name in lines[i] and attr_value in lines[i]:
return str(i + 1)
return None

# pylint: disable=too-many-return-statements
def get_config_type(self, option_name: str) -> ConfigType:
"""
Get config type based on the option name.
:param name: option name
:return: ConfigType
"""
option_name = option_name.lower()

if option_name.endswith("version"):
return ConfigType.VERSION_NUMBER

if option_name.endswith("mode"):
return ConfigType.MODE

if option_name.endswith("size"):
return ConfigType.SIZE

if option_name.endswith("port"):
return ConfigType.PORT

if option_name.endswith("host"):
return ConfigType.HOST

if option_name.endswith("path"):
return ConfigType.PATH

if option_name.endswith("permission"):
return ConfigType.PERMISSION

if option_name in (
"maxrecents",
"maxaspectratio",
"priority",
"height",
"width",
):
return ConfigType.NUMBER

if option_name in (
"name",
"label",
"description",
"process",
"package",
):
return ConfigType.NAME

return ConfigType.UNKNOWN
2 changes: 2 additions & 0 deletions src/cfgnet/plugins/plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
from cfgnet.plugins.concept.github_actions_plugin import GitHubActionPlugin
from cfgnet.plugins.concept.gradle_plugin import GradlePlugin
from cfgnet.plugins.concept.flutter_plugin import FlutterPlugin
from cfgnet.plugins.concept.android_plugin import AndroidPlugin


class PluginManager:
Expand Down Expand Up @@ -93,6 +94,7 @@ class PluginManager:
GitHubActionPlugin(),
GradlePlugin(),
FlutterPlugin(),
AndroidPlugin(),
]

file_type_plugins: List[Plugin] = [
Expand Down
50 changes: 50 additions & 0 deletions tests/cfgnet/plugins/concept/test_android_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# This file is part of the CfgNet module.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/>.

import os

import pytest

from cfgnet.plugins.concept.android_plugin import AndroidPlugin
from cfgnet.config_types.config_types import ConfigType
from tests.utility.id_creator import make_id


@pytest.fixture(name="get_plugin")
def get_plugin_():
plugin = AndroidPlugin()
return plugin


def test_is_responsible(get_plugin):
plugin = get_plugin

assert plugin.is_responsible("tests/files/AndroidManifest.xml")
assert not plugin.is_responsible("tests/files/pom.xml")


def test_config_types(get_plugin):
maven_plugin = get_plugin
maven_file = os.path.abspath("tests/files/AndroidManifest.xml")
artifact = maven_plugin.parse_file(maven_file, "AndroidManifest.xml")
nodes = artifact.get_nodes()

name_node = next(filter(lambda x: x.id == make_id("AndroidManifest.xml", "manifest", "package", "com.example.myfirstapp"), nodes))
bool_node = next(filter(lambda x: x.id == make_id("AndroidManifest.xml", "manifest", "application", "allowBackup", "true"), nodes))
version_node = next(filter(lambda x: x.id == make_id("AndroidManifest.xml", "manifest", "uses-sdk", "minSdkVersion", "21"), nodes))

assert version_node.config_type == ConfigType.VERSION_NUMBER
assert bool_node.config_type == ConfigType.BOOLEAN
assert name_node.config_type == ConfigType.NAME
3 changes: 0 additions & 3 deletions tests/cfgnet/plugins/concept/test_flutter_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ def test_config_types(get_plugin):
artifact = plugin.parse_file(file, "pubspec.yaml")
nodes = artifact.get_nodes()

for node in nodes:
print(node, node.config_type)

version_node = next(filter(lambda x: x.id == make_id("pubspec.yaml", "version", "1.0.0"), nodes))
name_node = next(filter(lambda x: x.id == make_id("pubspec.yaml", "name", "my_flutter_app"), nodes))
url_node = next(filter(lambda x: x.id == make_id("pubspec.yaml", "homepage", "https://example.com"), nodes))
Expand Down
4 changes: 3 additions & 1 deletion tests/cfgnet/plugins/test_plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
def test_get_all_plugins():
all_plugins = PluginManager.get_plugins()

assert len(all_plugins) == 31
assert len(all_plugins) == 32


def test_get_responsible_plugin():
Expand Down Expand Up @@ -56,6 +56,7 @@ def test_get_responsible_plugin():
github_action_plugin = PluginManager.get_responsible_plugin(plugins, ".github/workflows/test.yml")
gradle_plugin = PluginManager.get_responsible_plugin(plugins, "path/to/gradle.properties")
flutter_plugin = PluginManager.get_responsible_plugin(plugins, "path/to/pubspec.yaml")
android_plugin = PluginManager.get_responsible_plugin(plugins, "path/to/AndroidManifest.xml")

assert docker_plugin.concept_name == "docker"
assert maven_plugin.concept_name == "maven"
Expand Down Expand Up @@ -88,3 +89,4 @@ def test_get_responsible_plugin():
assert github_action_plugin.concept_name == "github-action"
assert gradle_plugin.concept_name == "gradle"
assert flutter_plugin.concept_name == "flutter"
assert android_plugin.concept_name == "android"
34 changes: 34 additions & 0 deletions tests/files/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myfirstapp">

<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Specify the minimum and target SDK version -->
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="33" />

<!-- Declare the application -->
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<!-- Define an activity that serves as the entry point -->
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".DisplayMessageActivity"
android:parentActivityName=".MainActivity" />
</application>
</manifest>

0 comments on commit da4111f

Please sign in to comment.